<?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: EffessDev</title>
    <description>The latest articles on DEV Community by EffessDev (@effessdev).</description>
    <link>https://dev.to/effessdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3932962%2Ff8063266-7ea2-44d6-8ff0-7fbce9748ba7.png</url>
      <title>DEV Community: EffessDev</title>
      <link>https://dev.to/effessdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/effessdev"/>
    <language>en</language>
    <item>
      <title>I Made a Video Editor Specifically for DEV.to Writers</title>
      <dc:creator>EffessDev</dc:creator>
      <pubDate>Wed, 30 Sep 2026 15:46:54 +0000</pubDate>
      <link>https://dev.to/effessdev/i-made-a-video-editor-specifically-for-devto-writers-2p3g</link>
      <guid>https://dev.to/effessdev/i-made-a-video-editor-specifically-for-devto-writers-2p3g</guid>
      <description>&lt;p&gt;How many times have you taken a &lt;strong&gt;screen recording&lt;/strong&gt; and wished you could &lt;strong&gt;quickly crop&lt;/strong&gt; the taskbar down there, &lt;strong&gt;trim the start and end&lt;/strong&gt; so no one sees you opening OBS Studio, &lt;strong&gt;cut and delete&lt;/strong&gt; the part where you fell asleep, &lt;strong&gt;export it&lt;/strong&gt; as an &lt;strong&gt;optimized&lt;/strong&gt; animated GIF or WebP, and drag and drop it directly to your DEV.to post?&lt;/p&gt;

&lt;p&gt;Opening DaVinci Resolve for this purpose is overkill. Even if you open it anyway, you will not be able to crop your video &lt;strong&gt;like this:&lt;/strong&gt;&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%2F78c5vkcnaxwe1vpmfxd0.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%2F78c5vkcnaxwe1vpmfxd0.png" alt=" " width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my opinion, this is the most useful feature of this app. Many video editors force you to pick a canvas resolution and won't allow you to change it this way.&lt;/p&gt;

&lt;p&gt;But other than that, it also supports the following features (note: these features were written by Gemini):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Non-Destructive Multi-Clip Timeline:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Split video segments anywhere (&lt;code&gt;S&lt;/code&gt; key).&lt;/li&gt;
&lt;li&gt;Drag and drop clips to reorder them on the fly.&lt;/li&gt;
&lt;li&gt;Drag clip edges to trim start and end points with live preview.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible Export Options:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Export to &lt;strong&gt;WebP&lt;/strong&gt;, &lt;strong&gt;GIF&lt;/strong&gt;, &lt;strong&gt;MP4&lt;/strong&gt;, &lt;strong&gt;WebM&lt;/strong&gt;, &lt;strong&gt;MOV&lt;/strong&gt;, or &lt;strong&gt;MKV&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Adjust framerate (0.5 to 60 FPS), scaling/resolution %, and quality settings (automatic palette generation with dither controls for GIFs).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast Timeline Navigation &amp;amp; Controls:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Scroll, zoom (&lt;code&gt;Ctrl + Wheel&lt;/code&gt;), and pan (&lt;code&gt;Middle-click drag&lt;/code&gt;) through complex timelines.&lt;/li&gt;
&lt;li&gt;Frame-by-frame navigation using arrow keys.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seamless Preview:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Automatic background proxy generation for codecs not natively supported by Qt.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As of now, it works, and it is useful. I have tested it quite a bit and fixed many issues. But still, it is &lt;strong&gt;not a polished product yet&lt;/strong&gt;, and it &lt;strong&gt;requires FFmpeg&lt;/strong&gt; to work. Installing FFmpeg is very easy. In Windows, just run &lt;code&gt;winget install Gyan.FFmpeg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I have opened a few issues myself. They contain a few things I have tried (but couldn't do), and also things that I'll add in the future:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/effessdev/GiffyPy/issues" rel="noopener noreferrer"&gt;https://github.com/effessdev/GiffyPy/issues&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app is made using &lt;strong&gt;Python&lt;/strong&gt; and &lt;strong&gt;PySide6&lt;/strong&gt;. Here is a link to the GitHub Repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/effessdev/GiffyPy" rel="noopener noreferrer"&gt;https://github.com/effessdev/GiffyPy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, do you find this useful? Have you been through this kind of friction before while writing in DEV? I'd love to hear your thoughts!&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Let Free Web Gemini Code My Terrarium, and Now the Worms are Having an Identity Crisis</title>
      <dc:creator>EffessDev</dc:creator>
      <pubDate>Mon, 28 Sep 2026 11:14:04 +0000</pubDate>
      <link>https://dev.to/effessdev/i-let-free-web-gemini-code-my-terrarium-and-now-the-worms-are-having-an-identity-crisis-57n2</link>
      <guid>https://dev.to/effessdev/i-let-free-web-gemini-code-my-terrarium-and-now-the-worms-are-having-an-identity-crisis-57n2</guid>
      <description>&lt;p&gt;I built a &lt;a href="https://github.com/effessdev/terrarium" rel="noopener noreferrer"&gt;terrarium simulation&lt;/a&gt; using Pygame a while back:&lt;/p&gt;

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

&lt;p&gt;Let's check whether Gemini can add entirely new features to this Python codebase without blowing everything up.&lt;/p&gt;

&lt;p&gt;Let's open up the project in VS Code:&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%2Fabal18gvu3tq84pltjh3.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%2Fabal18gvu3tq84pltjh3.png" alt="Project opened in VS Code" width="799" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am using &lt;a href="https://github.com/effessdev/reptclip-vscode" rel="noopener noreferrer"&gt;ReptClip for VS Code&lt;/a&gt; (an extension I built) to build the context and apply the diffs.&lt;/p&gt;

&lt;p&gt;Let's add&lt;code&gt;README.md&lt;/code&gt;, &lt;code&gt;requirements.txt&lt;/code&gt;, and all Python files into the context (I know it's not ideal, but let's try):&lt;/p&gt;

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

&lt;p&gt;Already 64,000 tokens. Let's paste it in:&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%2Fr05rtmfi4q3t0mem3fqh.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%2Fr05rtmfi4q3t0mem3fqh.png" alt=" " width="800" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What? The browser chat UI silently truncated the context I pasted in, without showing a single warning!&lt;/p&gt;

&lt;p&gt;Luckily, the limit is larger if you upload the context as 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F81e8jx86k1s4enm8bt4q.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%2F81e8jx86k1s4enm8bt4q.png" alt=" " width="799" height="226"&gt;&lt;/a&gt;&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%2Fgcdauxjed50c8596i3r2.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%2Fgcdauxjed50c8596i3r2.png" alt=" " width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's type the prompt and hit send!&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%2F52daxvjopz08ad9d0e09.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%2F52daxvjopz08ad9d0e09.png" alt=" " width="799" height="465"&gt;&lt;/a&gt;&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%2F7a0p7u86ydadudyh6clh.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%2F7a0p7u86ydadudyh6clh.png" alt=" " width="800" height="683"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A 108-line diff. Now, I can copy the diffs and click &lt;strong&gt;Apply Diffs&lt;/strong&gt; in my extension. It will automatically read them from the clipboard and apply the changes:&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%2Ffyk4us9hqxmmqc0wradw.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%2Ffyk4us9hqxmmqc0wradw.png" alt=" " width="800" height="82"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's try once more:&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%2Fx870a092du1qf55vi0s1.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%2Fx870a092du1qf55vi0s1.png" alt=" " width="799" height="353"&gt;&lt;/a&gt;&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%2Fv2ge0yewtk2fukduar6b.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%2Fv2ge0yewtk2fukduar6b.png" alt=" " width="800" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even though ReptClip supports fuzzy matching in SEARCH blocks, the AI hallucinated too much, so the extension wasn't able to find a match.&lt;/p&gt;

&lt;p&gt;Here is my new plan:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give the AI only the project structure, README, and &lt;code&gt;requirements.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ask it which files it needs to see to implement the feature&lt;/li&gt;
&lt;/ol&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%2Fk3x5lk681mskytuk5ncx.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%2Fk3x5lk681mskytuk5ncx.png" alt=" " width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It gave a list of the files it wants to see:&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%2Fxtw6r93ua80inf77jfi9.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%2Fxtw6r93ua80inf77jfi9.png" alt=" " width="799" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since we already added the project structure and output format, let's uncheck them and add the files Gemini asked for:&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%2F61ivkkxh0mupgl0rbweo.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%2F61ivkkxh0mupgl0rbweo.png" alt=" " width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luckily, this time it didn't get truncated:&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%2Ftwvwprleqw2sqgu82odp.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%2Ftwvwprleqw2sqgu82odp.png" alt=" " width="799" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The new diff was 165 lines:&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%2Fgk2f9vpurep16if0wf5g.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%2Fgk2f9vpurep16if0wf5g.png" alt=" " width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's apply the diffs:&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%2Fxcqs9lcdqvqm2dtnv0yf.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%2Fxcqs9lcdqvqm2dtnv0yf.png" alt=" " width="797" height="66"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only one fuzzy match. Let's launch the terrarium:&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%2Fo4e9dvd8tkicl7y98ccw.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%2Fo4e9dvd8tkicl7y98ccw.png" alt=" " width="799" height="447"&gt;&lt;/a&gt;&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%2Fclpihfr1leiq9khme3yb.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%2Fclpihfr1leiq9khme3yb.png" alt=" " width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There we go! We got the feature we asked for.&lt;/p&gt;

&lt;p&gt;Time to make it harder. The new feature:&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%2Fvjlic75460e5v214rte0.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%2Fvjlic75460e5v214rte0.png" alt=" " width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A suspiciously tiny diff that literally fits into a single screenshot:&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%2Fw534u1xke0tstrcqxfcc.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%2Fw534u1xke0tstrcqxfcc.png" alt=" " width="486" height="755"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I applied it anyway:&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%2Fwijgp34ujndpteh3lb0p.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%2Fwijgp34ujndpteh3lb0p.png" alt=" " width="800" height="62"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And ran the simulation:&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%2Fzc5945sy675lzqr038tf.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%2Fzc5945sy675lzqr038tf.png" alt=" " width="123" height="77"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The worms looked a bit different, but it was definitely far from what I asked. Let's try again with &lt;strong&gt;Gemini Pro&lt;/strong&gt;.&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%2Fx9nvg8ydqheab1ragfk1.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%2Fx9nvg8ydqheab1ragfk1.png" alt=" " width="799" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's using some big words:&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%2Fqgawphs80u3hb1viyo77.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%2Fqgawphs80u3hb1viyo77.png" alt=" " width="798" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It gave me a 265-line diff modifying 2 files. Let's see what it did.&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%2Fpm9hl938lrchittpwgkz.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%2Fpm9hl938lrchittpwgkz.png" alt=" " width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can't even see the worms.&lt;/p&gt;

&lt;p&gt;I thought maybe they were still growing. I waited in front of the simulation at &lt;strong&gt;8x speed&lt;/strong&gt; for days*:&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%2F0u3vc95hbr5asrsw4z8p.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%2F0u3vc95hbr5asrsw4z8p.png" alt=" " width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Still empty. Let's try with &lt;strong&gt;Gemini Extended Thinking&lt;/strong&gt;:&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%2Fupi849gg321lvpxuw057.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%2Fupi849gg321lvpxuw057.png" alt=" " width="797" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to Gemini, it found the &lt;strong&gt;exact&lt;/strong&gt; bug:&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%2Fkhvx2dfeplxvumpc5lzs.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%2Fkhvx2dfeplxvumpc5lzs.png" alt=" " width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I applied its recommended patch:&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%2Fhq9auye4toebgc6xm3i1.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%2Fhq9auye4toebgc6xm3i1.png" alt=" " width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Still no worms.&lt;/p&gt;

&lt;p&gt;Let me handle this alone without taking a screenshot every 5 seconds. I'll come back soon.&lt;/p&gt;

&lt;p&gt;🏃 ...&lt;/p&gt;

&lt;p&gt;So, according to Gemini, this was the reason:&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%2Fza82nyunmsd5ugodyy9b.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%2Fza82nyunmsd5ugodyy9b.png" alt=" " width="800" height="67"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anyway, I gave it &lt;code&gt;ant.py&lt;/code&gt; so it could refer to it and generate proper diffs. This time, I &lt;strong&gt;was&lt;/strong&gt; able to see something!&lt;/p&gt;

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

&lt;p&gt;Now THAT is what actual &lt;strong&gt;peristalsis&lt;/strong&gt; looks like!&lt;/p&gt;

&lt;p&gt;I think that's enough for now without breaking it even more.&lt;/p&gt;

</description>
      <category>watercooler</category>
      <category>ai</category>
      <category>python</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I Built a VS Code Extension to Paste Your Project into Free Chatbots and Apply the Diffs in One Click</title>
      <dc:creator>EffessDev</dc:creator>
      <pubDate>Sat, 26 Sep 2026 12:01:15 +0000</pubDate>
      <link>https://dev.to/effessdev/i-built-a-vs-code-extension-to-paste-your-project-into-free-chatbots-and-apply-the-diffs-in-one-5enn</link>
      <guid>https://dev.to/effessdev/i-built-a-vs-code-extension-to-paste-your-project-into-free-chatbots-and-apply-the-diffs-in-one-5enn</guid>
      <description>&lt;p&gt;Let me show exactly how it works with an example 🛻&lt;/p&gt;

&lt;p&gt;The extension adds a new "ReptClip" tab in the VS Code bottom panel:&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%2Fhtjmle5gawjndem0lqao.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%2Fhtjmle5gawjndem0lqao.png" alt=" " width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am going to use this extension to redesign my website's landing page. First, we need to prepare the context.&lt;/p&gt;

&lt;p&gt;Specify which files to add to the context in the "Files to include" input. You can use glob patterns like &lt;code&gt;**/*.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since I want to update the landing page, let's add &lt;code&gt;page.tsx&lt;/code&gt;. You will get suggestions as you type:&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%2Fo0ww3k385y5gea5xv58i.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%2Fo0ww3k385y5gea5xv58i.png" alt=" " width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Enter" or "Tab" to accept the suggestion:&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%2Fj4vtc0utxw35oogm2rvp.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%2Fj4vtc0utxw35oogm2rvp.png" alt=" " width="800" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If it matches any files, it will turn green. Otherwise, it will turn yellow.&lt;/p&gt;

&lt;p&gt;Let's add &lt;code&gt;AGENTS.md&lt;/code&gt; too:&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%2Fc4mwxy1rr6maodra209i.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%2Fc4mwxy1rr6maodra209i.png" alt=" " width="799" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This order is preserved in the context output. &lt;code&gt;page.tsx&lt;/code&gt; will appear after &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, click "Enter" again. The context will be copied &lt;strong&gt;directly to your clipboard&lt;/strong&gt;. It  looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Project structure&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;.gitattributes
.gitignore
AGENTS.md
CONTRIBUTING.md
LICENSE
README.md
pipxclip-config.toml
pyproject.toml
src/pipxclip/__init__.py
src/pipxclip/cli.py
src/pipxclip/config.py
tests/test_cli.py
tests/test_config.py
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gh"&gt;# Output format&lt;/span&gt;

Provide all file modifications as Search/Replace blocks inside a single 4-backtick code block.

&lt;span class="gu"&gt;## Formatting Rules&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; &lt;span class="gs"&gt;**File Headers:**&lt;/span&gt; Every set of edits must begin with the exact relative file path on its own line (e.g., &lt;span class="sb"&gt;`path/to/file.ext`&lt;/span&gt;), even if there is only a single file in the project.
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**Exact Matching:**&lt;/span&gt; The &lt;span class="sb"&gt;`SEARCH`&lt;/span&gt; block must contain an &lt;span class="ge"&gt;*exact, verbatim*&lt;/span&gt; copy of existing code, including identical whitespace, indentation, and surrounding context lines to uniquely identify the match.
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**Uniqueness:**&lt;/span&gt; Provide sufficient surrounding unchanged lines in the &lt;span class="sb"&gt;`SEARCH`&lt;/span&gt; block to ensure it matches exactly &lt;span class="gs"&gt;**one**&lt;/span&gt; location in the target file.
&lt;span class="p"&gt;4.&lt;/span&gt; &lt;span class="gs"&gt;**New Files &amp;amp; Deletions:**&lt;/span&gt;
&lt;span class="p"&gt;   -&lt;/span&gt; To create a new file, use an empty &lt;span class="sb"&gt;`SEARCH`&lt;/span&gt; block.
&lt;span class="p"&gt;   -&lt;/span&gt; To delete a file or a code block, use an empty &lt;span class="sb"&gt;`REPLACE`&lt;/span&gt; block.
&lt;span class="p"&gt;5.&lt;/span&gt; &lt;span class="gs"&gt;**No Placeholders:**&lt;/span&gt; Do not use ellipses (&lt;span class="sb"&gt;`...`&lt;/span&gt;), comments like &lt;span class="sb"&gt;`# rest of code unchanged`&lt;/span&gt;, or omitted lines inside &lt;span class="sb"&gt;`SEARCH`&lt;/span&gt; or &lt;span class="sb"&gt;`REPLACE`&lt;/span&gt; blocks.
&lt;span class="p"&gt;6.&lt;/span&gt; &lt;span class="gs"&gt;**Sequential Edits:**&lt;/span&gt; If editing multiple parts of the same file, list the &lt;span class="sb"&gt;`SEARCH/REPLACE`&lt;/span&gt; blocks in top-to-bottom order as they appear in the file.

&lt;span class="gu"&gt;## Example&lt;/span&gt;

&lt;span class="p"&gt;````&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;src/models/user.py
&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; SEARCH
class User:
    def __init__(self, name):
        self.name = name

    def to_dict(self):
        return {"name": self.name}
=======
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

    def to_dict(self):
        return {
            "name": self.name,
            "email": self.email,
        }
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; REPLACE

src/controllers/user_controller.py
&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; SEARCH
from src.models.user import User
=======
from src.models.user import User
from src.utils.validator import validate_email
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; REPLACE
&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; SEARCH
def create_user(name):
    return User(name)
=======
def create_user(name, email):
    validate_email(email)
    return User(name, email)
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; REPLACE

src/utils/validator.py
&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; SEARCH
=======
def validate_email(email):
    if "@" not in email:
        raise ValueError("Invalid email address")
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; REPLACE
&lt;span class="p"&gt;````&lt;/span&gt;

&lt;span class="gh"&gt;# AGENTS.md&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;Do not make mistakes.
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gh"&gt;# Prompt&lt;/span&gt;

&amp;lt;- Cursor lands here. You can quickly start typing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, go to any free chatbot and paste the context using &lt;code&gt;Ctrl + V&lt;/code&gt;:&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%2Ff5jxb8w7ehggk3yr6rud.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%2Ff5jxb8w7ehggk3yr6rud.png" alt=" " width="799" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Type your prompt, and send:&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%2Ftgrcjg5vs45qaqjx499x.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%2Ftgrcjg5vs45qaqjx499x.png" alt=" " width="799" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The chatbot will generate the diffs inside a code block:&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%2Fzlnp5io9qxom4slta3o0.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%2Fzlnp5io9qxom4slta3o0.png" alt=" " width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy it to your clipboard, and go back to VS Code.&lt;/p&gt;

&lt;p&gt;Click "Apply Diffs" in the ReptClip tab. The extension &lt;strong&gt;reads the diffs from the clipboard and applies them to the files&lt;/strong&gt;:&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%2Fem5eml6a20gn7xeooorr.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%2Fem5eml6a20gn7xeooorr.png" alt=" " width="798" height="61"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's start the development server and check out the new landing page:&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%2F1m22hg2bxii6w4bgvovc.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%2F1m22hg2bxii6w4bgvovc.png" alt=" " width="799" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The landing page has been redesigned.&lt;/p&gt;

&lt;p&gt;If you didn't like the changes, clicking "Revert" reverts all affected files to the previous state.&lt;/p&gt;

&lt;p&gt;If at least one of the diffs is faulty, you'll get this:&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%2Flx6xdv2btf4fo9sloz8x.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%2Flx6xdv2btf4fo9sloz8x.png" alt=" " width="800" height="59"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It also lets you customize the context:&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%2Fiwb89u8qect9oqqsdjtr.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%2Fiwb89u8qect9oqqsdjtr.png" alt=" " width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State is stored on a per-project basis.&lt;/strong&gt; So, when you come back, your configuration will not be gone or mixed up with other projects.&lt;/p&gt;

&lt;p&gt;If you want to use the extension purely for context, uncheck &lt;code&gt;Diff format&lt;/code&gt; to remove the &lt;code&gt;## Output format&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;Here are a few edge cases explained:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Files to include &lt;code&gt;**/*.py main.py&lt;/code&gt;: All Python files are included, but &lt;code&gt;main.py&lt;/code&gt; comes last.&lt;/li&gt;
&lt;li&gt;Files to include &lt;code&gt;**/*.py src/**/*.py&lt;/code&gt;: All Python files are included, but the Python files in &lt;code&gt;src&lt;/code&gt; come last.&lt;/li&gt;
&lt;li&gt;Files to include &lt;code&gt;**/*.py&lt;/code&gt;, files to exclude &lt;code&gt;secret.py&lt;/code&gt;: All Python files are included, except &lt;code&gt;secret.py&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;So, that's basically what the extension does. If you liked it, a ⭐ on GitHub would really help &lt;strong&gt;A LOT!&lt;/strong&gt; Please feel free to share it with friends or teammates who are looking for a $0 AI coding setup.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/effessdev/reptclip-vscode" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=effessdev.reptclip-for-vscode" rel="noopener noreferrer"&gt;VS Code Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open-vsx.org/extension/effessdev/reptclip-for-vscode" rel="noopener noreferrer"&gt;Open VSX&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's inspired by my CLI app &lt;a href="https://github.com/effessdev/pipxclip" rel="noopener noreferrer"&gt;PipxClip&lt;/a&gt;, which was formerly ReptClip, so I named it &lt;a href="https://github.com/effessdev/reptclip-vscode" rel="noopener noreferrer"&gt;ReptClip for VS Code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, please tell me what you think in the comments. It really helps me improve the extension further. Big thanks to &lt;a class="mentioned-user" href="https://dev.to/piekwerk"&gt;@piekwerk&lt;/a&gt; and &lt;a class="mentioned-user" href="https://dev.to/mansio"&gt;@mansio&lt;/a&gt; for their suggestions during development!&lt;/p&gt;

&lt;p&gt;Also, thanks to &lt;a class="mentioned-user" href="https://dev.to/unitbuilds"&gt;@unitbuilds&lt;/a&gt; for informing me about the limited-time free access to Qwen 3.8 Flash in Qoder. It wouldn't have been possible without it.&lt;/p&gt;

&lt;p&gt;I purposefully kept the source code minimal and lightweight. Feel free to submit &lt;a href="https://github.com/effessdev/reptclip-vscode/pulls" rel="noopener noreferrer"&gt;pull requests&lt;/a&gt;. I review them every day!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total cost to build this: $0.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I created this extension specifically to help developers who can't afford paid coding agents, giving everyone access to a smooth AI workflow for free.&lt;/p&gt;

&lt;p&gt;Thanks for reading 🙂🎉🎉&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>productivity</category>
      <category>ai</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Is DEV.to Analytics broken? 🤔</title>
      <dc:creator>EffessDev</dc:creator>
      <pubDate>Thu, 24 Sep 2026 12:47:37 +0000</pubDate>
      <link>https://dev.to/effessdev/is-devto-analytics-broken-3i7i</link>
      <guid>https://dev.to/effessdev/is-devto-analytics-broken-3i7i</guid>
      <description>&lt;p&gt;This is the traffic sources graph for a post I created a few hours ago:&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%2Fhz6yhzkn8ilpgd0oxbdl.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%2Fhz6yhzkn8ilpgd0oxbdl.png" alt=" " width="557" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's not just this post. Every single one of my posts is dominated by direct/unknown sources. Is it like that for everyone else in here? Or is it just me? It doesn't make any sense at all.&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>I Made a VS Code Extension to Copy Your Repo to Your Clipboard as Clean Markdown Context for Your Chatbot</title>
      <dc:creator>EffessDev</dc:creator>
      <pubDate>Thu, 24 Sep 2026 10:11:21 +0000</pubDate>
      <link>https://dev.to/effessdev/i-made-a-vs-code-extension-to-copy-your-repo-to-your-clipboard-as-clean-markdown-context-for-your-4j6l</link>
      <guid>https://dev.to/effessdev/i-made-a-vs-code-extension-to-copy-your-repo-to-your-clipboard-as-clean-markdown-context-for-your-4j6l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;NOTE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You are reading a post about an older version of the extension. Newer versions automatically apply the generated diffs from your clipboard and add many more features. Check out the &lt;a href="https://github.com/effessdev/reptclip-vscode" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt; for the most up-to-date details, or read this &lt;a href="https://dev.to/effessdev/i-built-a-vs-code-extension-to-paste-your-project-into-free-chatbots-and-apply-the-diffs-in-one-5enn"&gt;updated post&lt;/a&gt; on DEV.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;The UI:&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%2Fpov2cp13fihkv4osx643.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%2Fpov2cp13fihkv4osx643.png" alt=" " width="799" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Project structure&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;.gitattributes
.gitignore
AGENTS.md
CONTRIBUTING.md
LICENSE
README.md
assets/preview.webp
docs/me/prompt.md
docs/me/publishing.md
pyproject.toml
reptclip-config.toml
src/reptclip/__init__.py
src/reptclip/cli.py
src/reptclip/cli_parser.py
src/reptclip/clipboard.py
src/reptclip/config.py
src/reptclip/file_reader.py
src/reptclip/filters.py
src/reptclip/git_files.py
src/reptclip/markdown_builder.py
tests/test_cli.py
tests/test_config.py
tests/test_file_reader.py
tests/test_filters.py
tests/test_git_files.py
tests/test_markdown_builder.py
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gh"&gt;# AGENTS.md&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;Do not make mistakes.
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gh"&gt;# Prompt&lt;/span&gt;

&amp;lt;- Cursor lands here; you can quickly start typing!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, type whatever you want to copy, then press Enter. Context will be copied &lt;strong&gt;straight to your clipboard!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Also, you'll get suggestions as you type:&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%2F68ykkb513gigkc11bqnl.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%2F68ykkb513gigkc11bqnl.png" alt=" " width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;State is stored on a per-project basis. When you come back, your configuration will too!&lt;/p&gt;

&lt;p&gt;It's inspired by my CLI app &lt;a href="https://github.com/effessdev/pipxclip" rel="noopener noreferrer"&gt;PipxClip&lt;/a&gt;, which was formerly ReptClip. So I named it &lt;a href="https://github.com/effessdev/reptclip-vscode" rel="noopener noreferrer"&gt;ReptClip for VS Code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here are the key improvements from the CLI version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No need to provide quotes for glob patterns: This is impossible in the CLI&lt;/li&gt;
&lt;li&gt;Get suggestions as you type&lt;/li&gt;
&lt;li&gt;Syntax highlighting&lt;/li&gt;
&lt;li&gt;Intuitive UI (README not required!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's really useful for me because this is my workflow when I finish my GitHub Copilot free credits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy files&lt;/li&gt;
&lt;li&gt;Paste them into gemini.google.com&lt;/li&gt;
&lt;li&gt;Paste them back&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And repeat:&lt;/p&gt;

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

&lt;p&gt;If you also found this helpful, please consider giving a ⭐ on GitHub! It really helps &lt;strong&gt;A LOT!&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/effessdev/reptclip-vscode" rel="noopener noreferrer"&gt;ReptClip GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=effessdev.reptclip-for-vscode" rel="noopener noreferrer"&gt;VS Code Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open-vsx.org/extension/effessdev/reptclip-for-vscode" rel="noopener noreferrer"&gt;Open VSX&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for reading till the end! Please let me know what you think. I am really curious!&lt;/p&gt;

&lt;p&gt;Issues and PRs are welcome too!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>vscode</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I just did something my AI agents couldn't</title>
      <dc:creator>EffessDev</dc:creator>
      <pubDate>Sat, 12 Sep 2026 04:30:19 +0000</pubDate>
      <link>https://dev.to/effessdev/i-just-did-something-my-ai-agents-couldnt-pmi</link>
      <guid>https://dev.to/effessdev/i-just-did-something-my-ai-agents-couldnt-pmi</guid>
      <description>&lt;p&gt;It is funny how things work out. You can let AI tools try to fix a code problem for days, but nothing works until you finally step in and do it yourself. &lt;/p&gt;

&lt;p&gt;I was working on my ESP-IDF project and wanted to organize it better. My goal was to use the standard practice of keeping different pieces of code in a folder named &lt;code&gt;components&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try #1: Gemini 3.6 Flash
&lt;/h2&gt;

&lt;p&gt;I don't pay for AI, so I use the free web versions. I used a tool I built called &lt;a href="https://github.com/effessdev/reptclip" rel="noopener noreferrer"&gt;ReptClip&lt;/a&gt; to copy my project structure and files. Then, I asked the AI which files to move and what new code to put inside them. &lt;/p&gt;

&lt;p&gt;I did exactly what the AI said without questioning it. Suddenly, red error lines popped up in my code editor. Soon, the whole file was covered in errors. I told the AI about it. &lt;/p&gt;

&lt;p&gt;It gave me new steps, and I just followed them again. It still didn't work. &lt;/p&gt;

&lt;p&gt;I did this over and over for hours. I got very mad, called the AI stupid, and reset my working directory to start all over from scratch. I do not know why it failed at such an easy job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try #2: Gemini 3.1 Pro
&lt;/h2&gt;

&lt;p&gt;I thought the Pro version of the AI would do a better job. I tried the exact same steps and trusted it completely again. But I ended up with the exact same errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tries #3, #4, #5, ... #n
&lt;/h2&gt;

&lt;p&gt;I lost count of how many times I tried this. I tested Gemini Flash Extended, Gemini Pro Extended, DeepSeek v4 Flash, and DeepSeek v4 Pro. For some reason, absolutely &lt;strong&gt;nothing&lt;/strong&gt; worked. I wasted two whole days doing this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try #n + 1
&lt;/h2&gt;

&lt;p&gt;Next, I decided to try GitHub Copilot. I gave it instructions, and it spent about 30 minutes on the task. It finally gave me a nice ✅ emoji and said it was done. I thought my problems were over. &lt;/p&gt;

&lt;p&gt;I tried to build my project, but it failed. I looked at my code files, and there were &lt;strong&gt;error squiggles everywhere&lt;/strong&gt;, just like before. &lt;/p&gt;

&lt;p&gt;I gave up and left the project alone for a few days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try #n + 2
&lt;/h2&gt;

&lt;p&gt;After a break, I came back with a new plan. &lt;/p&gt;

&lt;p&gt;I went to gemini.google.com and chose the Pro model. I asked the same question. But this time, I stopped blindly pasting the whole file the AI gave me. Instead, I decided to &lt;strong&gt;edit the &lt;code&gt;#include&lt;/code&gt; directives and type the &lt;code&gt;CMakeLists.txt&lt;/code&gt; files by myself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When I moved my code into the &lt;code&gt;components&lt;/code&gt; folder, I noticed errors on the &lt;code&gt;#include&lt;/code&gt; lines. I realized right away that the &lt;code&gt;#include&lt;/code&gt; paths were the real issue. I told the AI that I could not add those specific headers. The AI said I needed to add the dependency components in &lt;code&gt;REQUIRES&lt;/code&gt; or &lt;code&gt;PRIV_REQUIRES&lt;/code&gt; in &lt;code&gt;CMakeLists.txt&lt;/code&gt;. Adding those fixed a few headers, but some specific headers still failed. &lt;/p&gt;

&lt;p&gt;Finally, I searched on Google to find out exactly which components have the definitions for those specific headers that were causing the issue. The answer was slightly different there.&lt;/p&gt;

&lt;p&gt;I changed that one single line of code, &lt;strong&gt;and it finally worked!&lt;/strong&gt; The ONLY problem was that all the AIs put the wrong component name in &lt;code&gt;REQUIRES&lt;/code&gt;, and none of them could figure out that was the issue. It is hard to believe I wasted weeks on such a tiny problem.&lt;/p&gt;

&lt;p&gt;I skipped over a fair bit of the debugging rabbit hole in this post because I honestly couldn't remember every single twist and turn 😅&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devjournal</category>
      <category>programming</category>
      <category>automation</category>
    </item>
    <item>
      <title>Can I remove the AI-generated tag from my AI-generated course?</title>
      <dc:creator>EffessDev</dc:creator>
      <pubDate>Tue, 08 Sep 2026 15:03:01 +0000</pubDate>
      <link>https://dev.to/effessdev/can-i-remove-the-ai-generated-tag-from-my-ai-generated-course-5b22</link>
      <guid>https://dev.to/effessdev/can-i-remove-the-ai-generated-tag-from-my-ai-generated-course-5b22</guid>
      <description>&lt;p&gt;I recently used DeepSeek v4 Pro to generate an embedded systems course for app, game, or web developers who want to transition into low-level programming and embedded C. I wrote a hyper-specific prompt and asked the AI to first list the chapters and their content. Then I generated each chapter individually. Right now the course has 10 chapters.&lt;/p&gt;

&lt;p&gt;Originally, I created this course for myself, and I've been actively using it. Before studying a new chapter, I paste the content back into an AI to check for factual errors. When something feels unclear or incomplete, I dig deeper by asking questions. Then integrate what I learn back into the course. Once I finish, I plan to have one of my college professors review it for any remaining issues.&lt;/p&gt;

&lt;p&gt;To stay transparent, I added an "AI-generated" badge at the top of every chapter. But at this point, I'm questioning whether that label still fits. I've put a lot of personal effort into reviewing, correcting, and expanding the material. The phrase "AI-generated" often implies low effort or minimal human involvement, and I don't think that describes my course anymore.&lt;/p&gt;

&lt;p&gt;So here's my question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Is it fair to change or remove the AI-generated tag after significant human effort?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'd love to hear how other people handle this, so I haven't asked this to an AI yet.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>career</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Getting Started with ESP-IDF in VS Code for ESP32: Development Environment Setup Guide</title>
      <dc:creator>EffessDev</dc:creator>
      <pubDate>Sat, 29 Aug 2026 03:25:58 +0000</pubDate>
      <link>https://dev.to/effessdev/beyond-arduino-getting-started-with-esp-idf-in-vs-code-for-esp32-3ed4</link>
      <guid>https://dev.to/effessdev/beyond-arduino-getting-started-with-esp-idf-in-vs-code-for-esp32-3ed4</guid>
      <description>&lt;p&gt;This is a step-by-step tutorial that explains how you can set up your development environment for working with ESP-IDF projects in &lt;strong&gt;VS Code&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install ESP-IDF
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install EIM
&lt;/h3&gt;

&lt;p&gt;Espressif Systems provides a graphical tool called &lt;strong&gt;EIM (ESP-IDF Installation Manager)&lt;/strong&gt; to install ESP-IDF. Click the link below to go to the official page to download EIM:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dl.espressif.com/dl/eim/" rel="noopener noreferrer"&gt;https://dl.espressif.com/dl/eim/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure you are in the "Online Installer" tab. The exact file to download depends on your system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windows: Download &lt;code&gt;eim-gui-windows-x64.exe&lt;/code&gt;. Run this installer to install EIM.&lt;/li&gt;
&lt;li&gt;Linux x64 (Ubuntu): Download and install the &lt;code&gt;.deb&lt;/code&gt; package (&lt;code&gt;eim-gui-linux-x64.deb&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Install ESP-IDF using EIM
&lt;/h3&gt;

&lt;p&gt;Now that we have installed EIM, let's install ESP-IDF using it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open EIM.&lt;/li&gt;
&lt;li&gt;Under "New Installation" click "Start Installation".&lt;/li&gt;
&lt;li&gt;Under "Easy Installation", click "Start Easy Installation" to install the latest stable version of ESP-IDF with default settings.&lt;/li&gt;
&lt;li&gt;If there are no problems, you will see the "Ready to Install" page. Click "Start Installation".&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Install ESP-IDF VS Code Extension
&lt;/h2&gt;

&lt;p&gt;We use this extension as a high-level wrapper for ESP-IDF. Most times, we do not use ESP-IDF directly. For example, if we need to compile our source code, we ask the extension to do it, which uses the ESP-IDF we just installed internally to to compile the source code.&lt;/p&gt;

&lt;p&gt;Install the extension named "ESP-IDF" by "Espressif Systems" in VS Code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify installation
&lt;/h3&gt;

&lt;p&gt;After installing, restart VS Code. Use the shortcut &lt;code&gt;Ctrl + Shift + P&lt;/code&gt; to open the &lt;strong&gt;command palette&lt;/strong&gt; (remember this shortcut, we are going to use it a lot). Inside the command palette, search &lt;code&gt;ESP-IDF&lt;/code&gt;. You will see many entries which start with &lt;code&gt;ESP-IDF:&lt;/code&gt;. Those commands are provided my the ESP-IDF extension. These commands are what we use for almost everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Note
&lt;/h3&gt;

&lt;p&gt;If you are not in an ESP-IDF project, you might see this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No standard ESP-IDF project was found in this workspace. Do you want to activate the ESP-IDF extension anyway?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to use the extension (in this case we do), you should click "Activate Anyway". Then only we can use the extension. If you mistakenly clicked "X", restart VS Code, wait for that to pop up again, and click "Activate Anyway".&lt;/p&gt;

&lt;p&gt;Keep this in mind since you might need this in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a New Project
&lt;/h2&gt;

&lt;p&gt;Like I said before, we are going to use the &lt;code&gt;ESP-IDF: New Project&lt;/code&gt; command from the command palette to create the project. Press &lt;code&gt;Ctrl + Shift + P&lt;/code&gt; to open the command palette.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From the command palette, search and click on &lt;code&gt;ESP-IDF: New Project&lt;/code&gt;, and wait.&lt;/li&gt;
&lt;li&gt;Select your ESP-IDF version (you might see only one version since we only installed one), and wait.&lt;/li&gt;
&lt;li&gt;A new tab will pop up. Inside that tab, under &lt;code&gt;ESP-IDF Templates&lt;/code&gt; select the &lt;code&gt;sample_project&lt;/code&gt; template and click the "Create Project" button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tab will refresh, and you will see a form to fill in your project details. Fill in the details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project name: Your project name.&lt;/li&gt;
&lt;li&gt;Project directory: Your project directory.&lt;/li&gt;
&lt;li&gt;ESP-IDF target: esp32.&lt;/li&gt;
&lt;li&gt;ESP-IDF board: Custom board.&lt;/li&gt;
&lt;li&gt;Serial port: Detect.&lt;/li&gt;
&lt;li&gt;OpenOCD configuration files: Keep the default value.&lt;/li&gt;
&lt;li&gt;ESP-IDF component directory: Keep the input empty.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click "Create Project" and wait again. After creation, in the new tab which pops up, click "Open Project". This will open your brand new ESP-IDF project in a fresh VS Code window.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are prompted to generate &lt;code&gt;compile_commands.json&lt;/code&gt;, accept it. If not, or you mistakenly clicked "Decline", do &lt;code&gt;Ctrl + Shift + P -&amp;gt; ESP-IDF: Run idf.py reconfigure Task&lt;/code&gt;, which does the same thing. &lt;code&gt;compile_commands.json&lt;/code&gt; is essential for Intellisense to work correctly. Otherwise, you will encounter error squiggles everywhere.&lt;/p&gt;

&lt;p&gt;If you are using Clangd instead of the Microsoft C/C++ extension, run &lt;code&gt;ESP-IDF: Configure project for ESP-Clang&lt;/code&gt; from the VS Code command palette to make sure Clangd IntelliSense works correctly. Also, make sure both of them aren't activated at the same time, as they can interfere with each other.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Configure your project
&lt;/h2&gt;

&lt;p&gt;You need to configure your project whenever you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start a new project (like we did now)&lt;/li&gt;
&lt;li&gt;Adding, removing, or renaming source files&lt;/li&gt;
&lt;li&gt;Adding or changing component dependencies&lt;/li&gt;
&lt;li&gt;Fixing corrupted build files&lt;/li&gt;
&lt;li&gt;Etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it's safe to do again and again, even if you haven't done anything above or have any problems. Since we just created a new project, let's run it.&lt;/p&gt;

&lt;p&gt;Like we did before, we are again going to use the command palette. Open the command palette (do you remember how?), and click &lt;code&gt;Ctrl + Shift + P -&amp;gt; ESP-IDF: Run idf.py reconfigure Task&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This will take some time. While it's working, let's learn what it does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generates (or regenerates) the build files&lt;/li&gt;
&lt;li&gt;Generates &lt;code&gt;compile_commands.json&lt;/code&gt;, which is essential for Microsoft C/C++ Extension or Clangd to provide Intellisense&lt;/li&gt;
&lt;li&gt;And a lot more (just know these two for now)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Let's write some code
&lt;/h2&gt;

&lt;p&gt;If you look at &lt;code&gt;main.c&lt;/code&gt; right now, you will see and empty main function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;app_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's log "Hello world!" inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;"esp_log.h"&lt;/span&gt;&lt;span class="c1"&gt; // Add this header for logging&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="c1"&gt;// Define a tag for your log messages&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MAIN"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;app_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Log "Hello world!"&lt;/span&gt;
  &lt;span class="n"&gt;ESP_LOGI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello world!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building (compiling) the project
&lt;/h2&gt;

&lt;p&gt;Like we always do, open the command palette and run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ESP-IDF: Build Your Project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will also take some time. Be patient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flashing your project
&lt;/h2&gt;

&lt;p&gt;Flashing is like uploading the compiled code to your ESP32. You need to physically connect the ESP32 to your computer. After doing that, run the following command from the command palette:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ESP-IDF: Flash (UART) Your Project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will probably won't work out of the box. The fix differ dipending on your platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you are using Windows
&lt;/h3&gt;

&lt;p&gt;If you are on Windows, flashing might show this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A fatal error occurred: Could not connect to an Espressif device on any of the 1 available serial ports.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that happens, the next time you try, press and hold the BOOT button on your board as soon as&lt;br&gt;
you see &lt;code&gt;Connecting...&lt;/code&gt;. After some time, the &lt;code&gt;Connecting.....&lt;/code&gt; will stop and you see several outputs. Stop holding the BOOT button when you see these somewhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Uploading stub flasher...
Running stub flasher...
Stub flasher running.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It might require some trial and error to find out how long you will have to hold the BOOT button.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you are using Linux (e.g., Ubuntu)
&lt;/h3&gt;

&lt;p&gt;You might get this error while flashing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;A fatal error occurred: Could not open /dev/ttyUSB0, the port is busy or doesn't exist.
([Errno 13] could not open port /dev/ttyUSB0: [Errno 13] Permission denied: '/dev/ttyUSB0')

Hint: Try to add user into dialout or uucp group.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that happens, run this command, log out, log back in (or reboot), and try flashing again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; dialout &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have to do it only once per system. Subsequent flashing will work without running this comamnd.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring the device
&lt;/h2&gt;

&lt;p&gt;You should keep your ESP32 connected to your computer so that it recieves power and be running. Let's check whether the "Hello world!" got logged.&lt;/p&gt;

&lt;p&gt;On the bottom of VS Code, you will see several icons. Find the icon which looks like a monitor and hover over it. If you see "Monitor Device" when you hover, you found the correct icon.&lt;/p&gt;

&lt;p&gt;Click on that icon. You will see several logs popping up. These are send internally. But among those log messages you will find our Hello world:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;I (258) main_task: Started on CPU0
I (258) main_task: Calling app_main()
I (258) MAIN: Hello world!   &amp;lt;--------------- this
I (258) main_task: Returned from app_main()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might see different numbers than 258. That's not a problem.&lt;/p&gt;

&lt;p&gt;So, congratulations! You just set up your computer for ESP-IDF, created a new project, wrote some code, compiled, flashed, and monitored the output! Next, you can check out the &lt;a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/index.html#" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; to learn more about ESP-IDF. I also have some great tutorials which you can &lt;a href="https://effessdev.github.io/read/" rel="noopener noreferrer"&gt;read on my website&lt;/a&gt; or on &lt;a href="https://dev.to/effessdev"&gt;DEV.to&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Using the &lt;code&gt;idf.py&lt;/code&gt; command
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;idf.py&lt;/code&gt; may not be available in your terminal, but it's always available in the ESP-IDF terminal, which you can open using &lt;code&gt;Ctrl + Shift + P -&amp;gt; ESP-IDF: Open ESP-IDF Terminal&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding the VS Code Configuration Folder
&lt;/h3&gt;

&lt;p&gt;If you accidentally deleted/edited the &lt;code&gt;.vscode&lt;/code&gt; folder, you can regenerate it using the &lt;code&gt;Ctrl + Shift + P -&amp;gt; ESP-IDF: Add VS Code Configuration Folder&lt;/code&gt; command.&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>arduino</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>ESP-IDF Bluetooth LE (BLE) Beginner Tutorial</title>
      <dc:creator>EffessDev</dc:creator>
      <pubDate>Fri, 28 Aug 2026 13:30:10 +0000</pubDate>
      <link>https://dev.to/effessdev/esp-idf-bluetooth-le-tutorial-53c</link>
      <guid>https://dev.to/effessdev/esp-idf-bluetooth-le-tutorial-53c</guid>
      <description>&lt;p&gt;This tutorial covers the basics of how to use Bluetooth Low Energy (BLE) in your ESP-IDF project. It's based on the &lt;a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/ble/get-started/ble-introduction.html" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; by Espressif Systems.&lt;/p&gt;

&lt;p&gt;This is a practical, step-by-step tutorial, focusing on building a conceptual understanding of BLE and its basic code implementation, rather than every single feature in detail. You can follow along from the beginning to the end. After completing this, you will be able to understand the &lt;a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/ble/get-started/ble-introduction.html" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; and the &lt;a href="https://github.com/espressif/esp-idf/tree/e37a7ae137c0ea6ebca90c0dd67f4a528d73b727/examples/bluetooth/ble_get_started/nimble/NimBLE_GATT_Server" rel="noopener noreferrer"&gt;official example&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can download the full final code &lt;a href="https://github.com/effessdev/esp-idf-examples/blob/main/examples/ble_led/main/main.c" rel="noopener noreferrer"&gt;here&lt;/a&gt; and the full project &lt;a href="https://github.com/effessdev/esp-idf-examples/tree/main/examples/ble_led" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but I recommend you to write the code yourself by following this tutorial instead.&lt;/p&gt;

&lt;p&gt;Let's start!&lt;/p&gt;

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

&lt;p&gt;I am assuming you have created an empty ESP-IDF project using the &lt;code&gt;sample_project&lt;/code&gt; template and verified building and flashing is working properly. If you haven't already, you can check out my tutorial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/effessdev/beyond-arduino-getting-started-with-esp-idf-in-vs-code-for-esp32-3ed4"&gt;Read on DEV.to&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://effessdev.github.io/read/esp-idf-vscode-setup-guide/" rel="noopener noreferrer"&gt;Read on my website&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Enabling BLE in ESP-IDF
&lt;/h2&gt;

&lt;p&gt;Before you can use BLE, you have to enable it using Menuconfig. There are two ways to do this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Using the VS Code Command Palette
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Only use this option if you are developing using the ESP-IDF VS Code extension.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;code&gt;Ctrl + Shift + P&lt;/code&gt; to open the command palette.&lt;/li&gt;
&lt;li&gt;Search for and select &lt;code&gt;ESP-IDF: SDK Configuration Editor (Menuconfig)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;Component Config -&amp;gt; Bluetooth&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check the Bluetooth box and select &lt;code&gt;NimBLE - BLE only&lt;/code&gt; from the Host selector.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Option 2: Using the ESP-IDF terminal
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the project configuration menu by running &lt;code&gt;idf.py menuconfig&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;Component config -&amp;gt; Bluetooth&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Enable Bluetooth by setting &lt;code&gt;Bluetooth&lt;/code&gt; to &lt;code&gt;[x]&lt;/code&gt; (enabled).&lt;/li&gt;
&lt;li&gt;Select the host stack by navigating to &lt;code&gt;Host&lt;/code&gt; and choosing &lt;code&gt;NimBLE - BLE only&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;S&lt;/code&gt; to save, then &lt;code&gt;Q&lt;/code&gt; to quit.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Updating &lt;code&gt;CMakeLists.txt&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;You should add &lt;code&gt;PRIV_REQUIRES bt nvs_flash esp_driver_gpio&lt;/code&gt; in your &lt;code&gt;CMakeLists.txt&lt;/code&gt; inside &lt;code&gt;idf_component_register()&lt;/code&gt;, since our program requires them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Including necessary headers
&lt;/h2&gt;

&lt;p&gt;This program requires a lot of headers to be included. Update your &lt;code&gt;main.c&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Includes */&lt;/span&gt;
&lt;span class="cm"&gt;/* STD APIs */&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;assert.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdbool.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cm"&gt;/* ESP APIs */&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;"driver/gpio.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"esp_log.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"nvs_flash.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"sdkconfig.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cm"&gt;/* FreeRTOS APIs */&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;freertos/FreeRTOS.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;freertos/task.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cm"&gt;/* NimBLE stack APIs */&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;"host/ble_hs.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"host/ble_uuid.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"host/util/util.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"nimble/ble.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"nimble/nimble_port.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"nimble/nimble_port_freertos.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"services/gap/ble_svc_gap.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"services/gatt/ble_svc_gatt.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cm"&gt;/* Defines */&lt;/span&gt;
&lt;span class="cp"&gt;#define TAG "NimBLE_LED"
#define DEVICE_NAME "NimBLE_LED"
&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;app_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Empty for now&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can change the &lt;code&gt;TAG&lt;/code&gt; and &lt;code&gt;DEVICE_NAME&lt;/code&gt; if required. &lt;code&gt;TAG&lt;/code&gt; is just a label which we use for logging (we will use it later in the tutorial). &lt;code&gt;DEVICE_NAME&lt;/code&gt; is how our ESP32 appears to the other devices (like our phone). Later, we are going to connect your phone to your ESP32. In your phone's "available devices" list, you will see this &lt;code&gt;DEVICE_NAME&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tips for beginners
&lt;/h3&gt;

&lt;p&gt;We use &lt;code&gt;#define&lt;/code&gt; to define macros. Macros are of two types - &lt;strong&gt;object-like macros&lt;/strong&gt; and &lt;strong&gt;function-like macros&lt;/strong&gt;. Here, &lt;code&gt;TAG&lt;/code&gt; and &lt;code&gt;DEVICE_NAME&lt;/code&gt; are object like macros. For now, let's only consider object-like macros (the ones we are using here).&lt;/p&gt;

&lt;p&gt;How it works is that, we define &lt;code&gt;DEVICE_NAME&lt;/code&gt; to be &lt;code&gt;"NimBLE_LED"&lt;/code&gt; and use &lt;code&gt;DEVICE_NAME&lt;/code&gt; like a variable in our code (e.g., we can call a function like this: &lt;code&gt;setDeviceName(DEVICE_NAME)&lt;/code&gt;). When we compile our code, before the compilation step begins, the preprocessor &lt;strong&gt;literally replaces all instances of &lt;code&gt;DEVICE_NAME&lt;/code&gt; with &lt;code&gt;"NimBLE_LED"&lt;/code&gt;&lt;/strong&gt;, so the previous function call becomes &lt;code&gt;setDeviceName("NimBLE_LED")&lt;/code&gt;. So, it is functionally identical to we directly using the string everywhere.&lt;/p&gt;

&lt;p&gt;The main reason why we use it is because the exact same string &lt;code&gt;"NimBLE_LED"&lt;/code&gt; will be used in multiple places in our code. If we use &lt;code&gt;#define&lt;/code&gt;, we can be 100% sure that it will be identical everywhere. Also, if we want to change the name of our device, we just have to update &lt;code&gt;DEVICE_NAME&lt;/code&gt; instead of eveywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize NVS Flash (Non-Volatile Storage)
&lt;/h2&gt;

&lt;p&gt;The BLE stack requires NVS flash to store configurations, so it should be initialized using &lt;code&gt;nvs_flash_init()&lt;/code&gt;. You don't need to overthink about it at this point. Just call it in the main function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;app_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;nvs_flash_init&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 function returns a return code. If the return code is &lt;code&gt;0&lt;/code&gt;, it means no errors. Any non-zero value means an error, and each value corresponds to a particular error. To make our code bulletproof, let's add some error handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;app_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;esp_err_t&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// To store the return code&lt;/span&gt;

  &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nvs_flash_init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ESP_ERR_NVS_NO_FREE_PAGES&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ESP_ERR_NVS_NEW_VERSION_FOUND&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ESP_ERROR_CHECK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nvs_flash_erase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nvs_flash_init&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="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;ESP_OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ESP_LOGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"failed to initialize nvs flash, error code: %d "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tips for beginners
&lt;/h3&gt;

&lt;p&gt;Here, &lt;code&gt;esp_err_t&lt;/code&gt; is just an &lt;code&gt;int&lt;/code&gt;, not some mysterious type. It's provided by &lt;code&gt;esp_err.h&lt;/code&gt;, which we included in &lt;code&gt;common.h&lt;/code&gt;. &lt;code&gt;esp_err_t&lt;/code&gt; stands for "ESP Error Type". That alias was defined for code readability.&lt;/p&gt;

&lt;p&gt;Similarly, &lt;code&gt;ESP_ERR_NVS_NO_FREE_PAGES&lt;/code&gt;, &lt;code&gt;ESP_ERR_NVS_NEW_VERSION_FOUND&lt;/code&gt;, etc. are also &lt;code&gt;int&lt;/code&gt;s, which are defined in &lt;code&gt;nvs.h&lt;/code&gt;. They are &lt;strong&gt;object-like macros&lt;/strong&gt; defined using &lt;code&gt;#define&lt;/code&gt; (just like how defined &lt;code&gt;TAG&lt;/code&gt; and &lt;code&gt;DEVICE_NAME&lt;/code&gt;), which means they will be replaced with their actual values (&lt;code&gt;4365&lt;/code&gt; and &lt;code&gt;4368&lt;/code&gt; in this case) in the preprocessing step.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ESP_LOGE&lt;/code&gt; ("E" stands for "Error") is a function-like macro used to log errors. They are similar to object-like macros, but instead of replacing with an object, it replace with a function. &lt;code&gt;ESP_LOGI&lt;/code&gt; is used for logging "Info".&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize GPIO2 output pin
&lt;/h2&gt;

&lt;p&gt;Here, we are using GPIO2 because most ESP32s have an inbuilt LED for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;app_main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;gpio_reset_pin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;gpio_set_direction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GPIO_MODE_OUTPUT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Initialize NimBLE stack
&lt;/h2&gt;

&lt;p&gt;Call &lt;code&gt;nimble_port_init()&lt;/code&gt; to initialize NimBLE, the software stack we are using to manage BLE.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;app_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nimble_port_init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;ESP_OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ESP_LOGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"failed to initialize nimble stack, error code: %d "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we are reusing the &lt;code&gt;ret&lt;/code&gt; variable we created earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize GAP
&lt;/h2&gt;

&lt;p&gt;GAP stands for Generic Access Profile. Initializing it makes our ESP32 discoverable to other devices (e.g., our phone).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;app_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="n"&gt;BaseType_t&lt;/span&gt; &lt;span class="n"&gt;rc&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="c1"&gt;// Still just an int; "rc" stands for "return code"&lt;/span&gt;

  &lt;span class="n"&gt;ble_svc_gap_init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ble_svc_gap_device_name_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEVICE_NAME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&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="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ESP_LOGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"failed to set device name to %s, error code: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;DEVICE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DEVICE_NAME&lt;/code&gt; is defined in &lt;code&gt;common.h&lt;/code&gt;. You can change it if you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize the GATT profile
&lt;/h2&gt;

&lt;p&gt;This is the hard part. GATT stands for Generic Attribute. It defines how two connected devices package, format, and send data using a structured hierarchy of services and characteristics.&lt;/p&gt;

&lt;p&gt;A GATT profile has one or more services, which have one or more characteristics within them, which hold data and descriptors. Each service and characteristic has a UUID. Here is a diagram showing the GATT hierarchy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------------+
|          PROFILE          |
+---------------------------+
|                           |
|  +---------------------+  |
|  | SERVICE             |  |
|  +---------------------+  |
|  |                     |  |
|  | CHARACTERISTIC      |  |
|  |  - PROPERTIES       |  |
|  |  - VALUE            |  |
|  |  - DESCRIPTOR       |  |
|  |                     |  |
|  +---------------------+  |
|  |                     |  |
|  |  CHARACTERISTIC     |  |
|  |  - PROPERTIES       |  |
|  |  - VALUE            |  |
|  |  - DESCRIPTOR       |  |
|  |                     |  |
|  +---------------------+  |
|                           |
|  +---------------------+  |
|  | SERVICE             |  |
|  +---------------------+  |
|  |                     |  |
|  | CHARACTERISTIC      |  |  
|  |  - PROPERTIES       |  |
|  |  - VALUE            |  |
|  |  - DESCRIPTOR       |  |
|  |                     |  |
|  +---------------------+  |
|                           |
+---------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, you can create a "Heart Rate" service that has a "Heart Rate Measurement" characteristic, which holds the value.&lt;/p&gt;

&lt;p&gt;Before defining our services and characteristics, let's initialize GATT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;app_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="n"&gt;ble_svc_gatt_init&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;h3&gt;
  
  
  Define GATT services table
&lt;/h3&gt;

&lt;p&gt;Here is where we define which services and characteristics our ESP32 have which can be tweaked from our phone. Define these before (outside) the &lt;code&gt;app_main&lt;/code&gt; function, but after the &lt;code&gt;#include&lt;/code&gt;s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ble_uuid16_t&lt;/span&gt; &lt;span class="n"&gt;auto_io_svc_uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BLE_UUID16_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x1815&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ble_uuid16_t&lt;/span&gt; &lt;span class="n"&gt;led_chr_uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BLE_UUID16_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x2A56&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;led_chr_val_handle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;led_chr_access&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;conn_handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;attr_handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ble_gatt_access_ctxt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ctxt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;span class="cm"&gt;/* GATT services table */&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ble_gatt_svc_def&lt;/span&gt; &lt;span class="n"&gt;gatt_svr_svcs&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="cm"&gt;/* Automation IO service */&lt;/span&gt;
    &lt;span class="p"&gt;{.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BLE_GATT_SVC_TYPE_PRIMARY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;auto_io_svc_uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;characteristics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ble_gatt_chr_def&lt;/span&gt;&lt;span class="p"&gt;[]){&lt;/span&gt;
             &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* LED characteristic */&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;led_chr_uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;access_cb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;led_chr_access&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BLE_GATT_CHR_F_WRITE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val_handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;led_chr_val_handle&lt;/span&gt;&lt;span class="p"&gt;},&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="cm"&gt;/* No more characteristics in this service. */&lt;/span&gt;
             &lt;span class="p"&gt;}}},&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="cm"&gt;/* No more services. */&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;You might wonder why we used &lt;code&gt;0x1815&lt;/code&gt; as the UUID of our Automation IO service and &lt;code&gt;0x2A56&lt;/code&gt; as the UUID of our LED characteristic. These short UUIDs are standardized by the &lt;strong&gt;Bluetooth SIG (Special Interest Group)&lt;/strong&gt;. Just like how the UNICODE value &lt;code&gt;U+1F60A&lt;/code&gt; corresponds to the smiling face emoji (😊), &lt;code&gt;0x1815&lt;/code&gt; corresponds to "Automation IO" and &lt;code&gt;0x2A56&lt;/code&gt; corresponds to "Digital", meaning that any device that connects to our ESP32 will decide that this service is for pin control and I/O automation when it sees the UUID &lt;code&gt;0x1815&lt;/code&gt;. There are even more such standardized UUIDs with their own specific meaning. You can check the Bluetooth SIG Official List for all assigned 16-bit and 32-bit UUIDs.&lt;/p&gt;

&lt;p&gt;Later in this tutorial, we are going to connect your phone to your ESP32, and you will see our service listed as "Automation IO". So keep this in mind.&lt;/p&gt;

&lt;p&gt;You can also use your own UUIDs instead of these standardized UUIDs, but make sure to use a 128bit UUID instead to make sure you don't accidentally use a standardized UUID, which can be misleading.&lt;/p&gt;

&lt;p&gt;Here, &lt;code&gt;{0}&lt;/code&gt; acts as the null terminator. It is required to denote the end of the array.&lt;/p&gt;

&lt;p&gt;Now we have defined the services tables. Let's define the &lt;code&gt;led_chr_access&lt;/code&gt; callback.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the Access Callback
&lt;/h3&gt;

&lt;p&gt;The access callback function handles incoming requests from connected clients. Since we configured our LED characteristic with &lt;code&gt;BLE_GATT_CHR_F_WRITE&lt;/code&gt;, NimBLE will invoke this function whenever a client (such as your phone) sends a write command.&lt;/p&gt;

&lt;p&gt;Copy the following code below the GATT services table, but above the main function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;led_chr_access&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;conn_handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;attr_handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ble_gatt_access_ctxt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ctxt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;arg&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="n"&gt;ctxt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;BLE_GATT_ACCESS_OP_WRITE_CHR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Extract the incoming byte payload&lt;/span&gt;
        &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctxt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;om&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;om_data&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&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="n"&gt;gpio_set_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_2&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="n"&gt;ESP_LOGI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"LED turned ON"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&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="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;gpio_set_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_2&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="n"&gt;ESP_LOGI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"LED turned OFF"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ESP_LOGW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Invalid payload value received: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BLE_ATT_ERR_UNLIKELY&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;Here, &lt;code&gt;ctxt-&amp;gt;op&lt;/code&gt; indicates what operation triggered the callback. We check if it equals &lt;code&gt;BLE_GATT_ACCESS_OP_WRITE_CHR&lt;/code&gt;. &lt;code&gt;ctxt-&amp;gt;om&lt;/code&gt; points to an &lt;code&gt;os_mbuf&lt;/code&gt; buffer structure containing the received payload data. &lt;code&gt;ctxt-&amp;gt;om-&amp;gt;om_data[0]&lt;/code&gt; reads the first byte. &lt;code&gt;gpio_set_level()&lt;/code&gt; sets the voltage of GPIO pin 2, which corresponds to the built-in LED in most ESP32s. If you don't have it, you can change the pin or connect an external LED.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registering Services and Characteristics
&lt;/h2&gt;

&lt;p&gt;Now that our GATT table and access callback are defined, we must register them with the NimBLE stack. Paste the following code in &lt;code&gt;app_main()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;app_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ble_gatts_count_cfg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gatt_svr_svcs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&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="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ESP_LOGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"failed to count gatt services, error code: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ble_gatts_add_svcs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gatt_svr_svcs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&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="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ESP_LOGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"failed to add gatt services, error code: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advertising
&lt;/h2&gt;

&lt;p&gt;To enable smartphones or central devices to discover your ESP32, you must start advertising. Advertising is like sending the signals so that other devices (like your phone) can find your ESP32.&lt;/p&gt;

&lt;p&gt;Define an &lt;code&gt;on_sync&lt;/code&gt; callback function that handles advertising. You don't really need to worry about this function right now, just copy it into your file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;gatt_svr_on_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ble_hs_adv_fields&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;memset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;fields&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="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// Make device discoverable and broadcast the name&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BLE_HS_ADV_F_DISC_GEN&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;BLE_HS_ADV_F_BREDR_UNSUP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;DEVICE_NAME&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEVICE_NAME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name_is_complete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ble_gap_adv_set_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&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="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ESP_LOGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"failed to set advertising data, error code: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Configure advertising behavior&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ble_gap_adv_params&lt;/span&gt; &lt;span class="n"&gt;adv_params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;memset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;adv_params&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="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adv_params&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;adv_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conn_mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BLE_GAP_CONN_MODE_UND&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Undirected connectable&lt;/span&gt;
    &lt;span class="n"&gt;adv_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disc_mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BLE_GAP_DISC_MODE_GEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// General discoverable&lt;/span&gt;

    &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ble_gap_adv_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BLE_OWN_ADDR_PUBLIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BLE_HS_FOREVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;adv_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&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="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ESP_LOGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"failed to start advertising, error code: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;ESP_LOGI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Advertising started successfully"&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;Now, assign the sync callback in the main function so that it runs at the correct time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;app_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;ble_hs_cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync_cb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gatt_svr_on_sync&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running the NimBLE Task
&lt;/h2&gt;

&lt;p&gt;NimBLE operates asynchronously within its own FreeRTOS task context. Define a dedicated host task function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;nimble_host_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;nimble_port_run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Runs continuously to handle stack events&lt;/span&gt;
    &lt;span class="n"&gt;nimble_port_freertos_deinit&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;Start the task from the main function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;app_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;nimble_port_freertos_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nimble_host_task&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;Now you have done the coding part. Let's test your device!&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Your ESP32 BLE Server
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Build and flash the code to your ESP32.&lt;/li&gt;
&lt;li&gt;Install nRF Connect on your mobile phone.&lt;/li&gt;
&lt;li&gt;Enable Bluetooth on your phone.&lt;/li&gt;
&lt;li&gt;Open nRF Connect and scan for nearby devices.&lt;/li&gt;
&lt;li&gt;Select your ESP32 device using the provided name (&lt;code&gt;DEVICE_NAME&lt;/code&gt;) and tap &lt;strong&gt;Connect&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Locate the &lt;strong&gt;Automation IO&lt;/strong&gt; service (&lt;code&gt;0x1815&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;Digital&lt;/strong&gt; characteristic (&lt;code&gt;0x2A56&lt;/code&gt;) and tap the Write action.&lt;/li&gt;
&lt;li&gt;Send a payload of &lt;code&gt;01&lt;/code&gt; (type = BYTE) to turn GPIO2 High (LED ON), or send &lt;code&gt;00&lt;/code&gt; to switch it Low (LED OFF).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your LED responded, congrats! You just successfuly created a bluetooth-controlled IoT device and controlled it using your phone!&lt;/p&gt;

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

&lt;p&gt;Now you have built the foundations of BLE. To learn more about BLE, check out the &lt;a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/ble/get-started/ble-introduction.html" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; and the &lt;a href="https://github.com/espressif/esp-idf/tree/e37a7ae137c0ea6ebca90c0dd67f4a528d73b727/examples/bluetooth/ble_get_started/nimble/NimBLE_GATT_Server" rel="noopener noreferrer"&gt;official example&lt;/a&gt;. Since you now know the basic concepts, the docs will feel much easier to digest.&lt;/p&gt;

&lt;p&gt;If you found this tutorial helpful, please let me know!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>iot</category>
      <category>esp32</category>
      <category>education</category>
    </item>
    <item>
      <title>I Built an AI Memory App That Lets You See, Edit, and Control Everything It Remembers</title>
      <dc:creator>EffessDev</dc:creator>
      <pubDate>Fri, 21 Aug 2026 16:02:42 +0000</pubDate>
      <link>https://dev.to/effessdev/i-built-an-ai-memory-app-that-lets-you-see-edit-and-control-everything-it-remembers-404d</link>
      <guid>https://dev.to/effessdev/i-built-an-ai-memory-app-that-lets-you-see-edit-and-control-everything-it-remembers-404d</guid>
      <description>&lt;p&gt;Ever forgotten when you last had a tetanus vaccine? Or what your blood test results were from two years ago?&lt;/p&gt;

&lt;p&gt;I ran into this problem myself, so I built &lt;a href="https://mmorganized.vercel.app" rel="noopener noreferrer"&gt;MmOrganized&lt;/a&gt; - an AI-powered memory app that gives you full visibility and control over what the AI remembers about you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Default AI Memory
&lt;/h2&gt;

&lt;p&gt;ChatGPT and Gemini both have memory features now, which is great. But they have a specific limitation: you cannot (easily) see what they remember, edit mistakes, or delete incorrect information. You are essentially trusting a black box to store your personal data accurately.&lt;/p&gt;

&lt;p&gt;For something as critical as health information, this was a dealbreaker for me. I needed to know exactly what was being stored and be able to fix it if something was wrong (I'm not saying the ChatGPT/Gemini memory is bad; they are just meant for a different purpose).&lt;/p&gt;

&lt;h2&gt;
  
  
  How MmOrganized Works
&lt;/h2&gt;

&lt;p&gt;The app uses a familiar chat interface. You sign up with your Google or GitHub account, and you are ready to go. The concept is simple:&lt;/p&gt;

&lt;p&gt;You type anything you want the AI to remember. For example, you can say "I took a tetanus vaccine today" or "My blood pressure was 120/80 yesterday". The AI processes your input and saves it to your personal memory store.&lt;/p&gt;

&lt;p&gt;When you need to retrieve that information later, you switch to Recall mode with a single tap and ask naturally: "When did I last get a tetanus shot?" The AI searches your memory and gives you an immediate answer. You can even continue the conversation with follow-up questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Control Over Your Memories
&lt;/h2&gt;

&lt;p&gt;This is where MmOrganized differs from other AI memory solutions. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;View everything&lt;/strong&gt; - Every single memory the AI has saved about you is displayed exactly when it gets inserted, along with a handy delete button. Nothing passes in without you knowing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manual control&lt;/strong&gt; - Add, edit, search, and reorder memories completely manually if you want.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Delete your data&lt;/strong&gt; - Permanently delete your entire account and data whenever you want.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Full Data Portability with Text Files
&lt;/h2&gt;

&lt;p&gt;One of the most important features is the import and export functionality. You can download your entire memory as a simple text file. This is not some proprietary format that locks you in. It is plain text that you can open in Notepad, edit with any text editor, or even copy and paste into another chatbot.&lt;/p&gt;

&lt;p&gt;You can then upload it back to MmOrganized with options to either replace your existing memory or append the new content. This gives you complete ownership of your data. You are never locked into the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technology Behind It
&lt;/h2&gt;

&lt;p&gt;It's a full-stack Next.js app, using Shadcn UI, Drizzle ORM, Motion, Better Auth, Pusher, etc. I have also built a few custom components and animations to give some character to the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notifications
&lt;/h2&gt;

&lt;p&gt;I also gave the AI the ability to send you notifications. Not particularly useful, but cool. But notifications in general have other purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Comes Next
&lt;/h2&gt;

&lt;p&gt;This currently does everything I want it to do, so I am unsure what to add next. What would make this more useful for you? What features are currently missing? Please try it out and let me know what you think below. &lt;/p&gt;

&lt;p&gt;Also, my first video edit, just go easy on me 🥺&lt;/p&gt;

&lt;p&gt;Link: &lt;a href="https://mmorganized.vercel.app" rel="noopener noreferrer"&gt;https://mmorganized.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Note: This is an older project that I personally still use heavily. It's not under active development because it already checks all my personal boxes. But if you have a killer feature idea, I will definitely consider it!)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>showdev</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Change touchpad scroll speed in Ubuntu (no terminal, easiest &amp; fastest method)</title>
      <dc:creator>EffessDev</dc:creator>
      <pubDate>Sat, 08 Aug 2026 12:33:12 +0000</pubDate>
      <link>https://dev.to/effessdev/change-touchpad-scroll-speed-in-ubuntu-no-terminal-easiest-fastest-method-5aj5</link>
      <guid>https://dev.to/effessdev/change-touchpad-scroll-speed-in-ubuntu-no-terminal-easiest-fastest-method-5aj5</guid>
      <description>&lt;p&gt;Here is how you can reduce the two-finger touchpad scroll speed in Ubuntu without using the terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Wayland Scroll Factor
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to the official release page: &lt;a href="https://github.com/daniel-g-carrasco/wayland-scroll-factor/releases/tag/v1.0.0" rel="noopener noreferrer"&gt;https://github.com/daniel-g-carrasco/wayland-scroll-factor/releases/tag/v1.0.0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Scroll down to the "Assets" section and click &lt;code&gt;wayland-scroll-factor_1.0.0-1_amd64.deb&lt;/code&gt; to download it.&lt;/li&gt;
&lt;li&gt;Double-click the downloaded file. A new "App Center" window will open.&lt;/li&gt;
&lt;li&gt;Click "Install" to install the app.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 2: Launch the app
&lt;/h2&gt;

&lt;p&gt;Open the Wayland Scroll Factor app:&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%2Fim4bh2m5xubp3t5bh13o.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%2Fim4bh2m5xubp3t5bh13o.png" alt="Opening Wayland Scroll Factor" width="800" height="511"&gt;&lt;/a&gt;&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%2F6dhapx6xgrb435ixm08p.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%2F6dhapx6xgrb435ixm08p.png" alt="Wayland Scroll Factor Window" width="799" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Adjust
&lt;/h2&gt;

&lt;p&gt;Adjust the speed using the sliders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing in other operating systems
&lt;/h2&gt;

&lt;p&gt;Visit &lt;a href="https://github.com/daniel-g-carrasco/wayland-scroll-factor" rel="noopener noreferrer"&gt;https://github.com/daniel-g-carrasco/wayland-scroll-factor&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ubuntu</category>
      <category>tooling</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
