<?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: Kevin Alemán</title>
    <description>The latest articles on DEV Community by Kevin Alemán (@kaleman15).</description>
    <link>https://dev.to/kaleman15</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%2F255126%2F6bdaeb84-6c1e-4e76-9d45-54c59eaac908.jpeg</url>
      <title>DEV Community: Kevin Alemán</title>
      <link>https://dev.to/kaleman15</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kaleman15"/>
    <language>en</language>
    <item>
      <title>Gotta Earn 'Em All: The Gym Badges of Agentic Engineering (Part 2)</title>
      <dc:creator>Kevin Alemán</dc:creator>
      <pubDate>Fri, 03 Jul 2026 02:35:20 +0000</pubDate>
      <link>https://dev.to/kaleman15/gotta-earn-em-all-the-gym-badges-of-agentic-engineering-part-2-5a11</link>
      <guid>https://dev.to/kaleman15/gotta-earn-em-all-the-gym-badges-of-agentic-engineering-part-2-5a11</guid>
      <description>&lt;p&gt;Last time we earned four badges: bedrock, context, scoped speed, and the patience to garden instead of shaking a vending machine. If you missed it, it's linked above. The guard at the gate hasn't moved. But you did.&lt;/p&gt;

&lt;p&gt;Quick refresher if you're just walking in: by &lt;em&gt;"agentic engineering"&lt;/em&gt; I mean using AI agents like Claude Code or Copilot to build real software, not just tab-complete. And fair warning, one of today's badges comes with receipts. 211 million lines of code worth.&lt;/p&gt;

&lt;p&gt;The first four gyms give you the basics. Useful! But nobody becomes Champion by learning just the basics. Past Celadon the game changes: it stops being about doing the work and starts being about &lt;em&gt;not getting fooled by it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's finish 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%2Fw5yi1dlx95vdfhjcdext.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%2Fw5yi1dlx95vdfhjcdext.png" alt="Nintendo Pokemon Fire Red Koga Battle" width="750" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Badge 5: Soul Badge: learn to taste poison
&lt;/h2&gt;

&lt;p&gt;Koga. Poison type. His gym is a maze of invisible walls, so you spend twenty minutes walking face-first into nothing while ninjas watch you. Foreshadowing.&lt;/p&gt;

&lt;p&gt;This might be THE badge of the agent era, and the hardest to teach. Not spotting code that's &lt;em&gt;wrong&lt;/em&gt;. Spotting code that's wrong while looking completely, beautifully right.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// looks done. runs. passes the demo.&lt;/span&gt;
&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;All notifications sent!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// what actually waits&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;All notifications sent!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every line in the first block is real, and the 3-user demo works. But &lt;code&gt;forEach&lt;/code&gt; ignores your &lt;code&gt;await&lt;/code&gt;: the log prints before a single notification goes out, and if one fails, the error evaporates into the void. Nothing is broken enough to fail. Everything is broken enough to hurt you in two weeks.&lt;/p&gt;

&lt;p&gt;That's what poison tastes like. Not bitter. Slightly, &lt;em&gt;slightly&lt;/em&gt; off.&lt;/p&gt;

&lt;p&gt;And you can't outsource the taste test: asking an LLM "is this slop?" can produce slop in the answer. Tasting is a human skill, and you earn it by having eaten some poison before. (Hello again, Badge 1.)&lt;/p&gt;

&lt;p&gt;If you can't tell legit from slightly-off, the agent is driving and you're asleep in the passenger seat. Don't offload your life to the agents. Control them. You are the human in charge.&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%2Fm1qfvtmsw3ztqgxspko6.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%2Fm1qfvtmsw3ztqgxspko6.png" alt="Nintendo Pokemon Fire Red Sabrina Battle" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Badge 6: Marsh Badge: it's not telepathy
&lt;/h2&gt;

&lt;p&gt;At some point the agent finishes your thought, matches your style, and your brain quietly files it under "understands me."&lt;/p&gt;

&lt;p&gt;It doesn't. What actually happens when you get good is way less mystical: &lt;strong&gt;you build a mental model of how it screws up.&lt;/strong&gt; It invents APIs with total confidence. It knows nothing newer than its training. And it will happily agree with a wrong premise if you say it like you mean it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// what most people send
"since the bug is in the cache layer, fix the cache invalidation"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// what you could be doing
"users report stale data after profile updates.
here's the request flow and the cache config.
where would YOU look first, and why?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;State it with enough conviction and the agent will find you a bug in the cache, whether one lives there or not. Ask it to reason first and it suddenly can.&lt;/p&gt;

&lt;p&gt;Prompting is talking to a very smart coworker with amnesia about your project and a habit of bluffing. Internalize that and half your prompts fix themselves for free.&lt;/p&gt;

&lt;p&gt;There's plenty of "skills" that would help you on this matter. A few examples:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/multica-ai/andrej-karpathy-skills" rel="noopener noreferrer"&gt;https://github.com/multica-ai/andrej-karpathy-skills&lt;/a&gt;&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://claude.com/plugins/superpowers" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.prod.website-files.com%2F6889473510b50328dbb70ae6%2F68c469d23594abeb9ab6ee48_og-claude-generic.jpg" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://claude.com/plugins/superpowers" rel="noopener noreferrer" class="c-link"&gt;
            Superpowers Plugin | Claude by Anthropic
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Claude learns brainstorming, subagent development with code review, debugging, TDD, and skill authoring through Superpowers.. Install this plugin to extend your workflow.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.prod.website-files.com%2F6889473510b50328dbb70ae6%2F689f4a9aff1f63fde75cf733_favicon.png" width="32" height="32"&gt;
          claude.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Use them. They're not magic, they're frameworks. You can still produce bad things with them. But they'll help you not to shoot yourself in the foot too frequently.&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%2F0zp38ogp2gfktqla9mjf.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%2F0zp38ogp2gfktqla9mjf.png" alt="Nintendo Pokemon Fire Red Blaine Battle" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Badge 7: Volcano Badge: the fire doesn't care who wrote the code
&lt;/h2&gt;

&lt;p&gt;Blaine makes you answer quiz questions before he'll even battle you. Which is just debugging with extra steps: you answer the riddles, then you get the fight.&lt;/p&gt;

&lt;p&gt;Generating code is the tutorial. This badge is everything &lt;em&gt;after&lt;/em&gt; it ships, when the thing the agent wrote is live and gently combusting at 2 AM. No clever prompt saves you here. You need logs you can actually read. Traces. The hard-won knowledge that one container's JSON log file can quietly eat a whole disk while you sleep (don't ask how I know. ok, ask, it was bad).&lt;/p&gt;

&lt;p&gt;Unlike the other badges, this one has a concrete training regime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Make your logs readable before you need them.&lt;/strong&gt; Structured logs with request IDs, not &lt;code&gt;console.log("here 2")&lt;/code&gt;. Future-you at 2 AM is a dumber person. Write for him.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learn one tracing tool.&lt;/strong&gt; Any. "Where did this request die" should be a query, not a vibe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read your own logs cold.&lt;/strong&gt; Trace one request end to end on a normal day. If you can't follow it when nothing's broken, you have zero chance when everything is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat your agent as a coworker.&lt;/strong&gt; Share with it all the context you would share with a human: stack traces, logs, screenshots, etc. Anything that it can use to understand and help you investigate. That's very useful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This also means you should &lt;em&gt;know your product&lt;/em&gt; even if you vibe coded it. If you don't, then when the fire happens and Claude is down (and that happens very often, recently) you will be alone with your thoughts, and with some code that you've never seen in your life...&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%2F6b8yv47770iu3l0w15z8.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%2F6b8yv47770iu3l0w15z8.png" alt="Nintendo Pokemon Fire Red Giovanni Battle" width="750" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Badge 8: Earth Badge: it's your name on the commit
&lt;/h2&gt;

&lt;p&gt;I said I wasn't explaining this one until we got here, so: Giovanni isn't just the eighth gym leader. He's the &lt;strong&gt;boss of Team Rocket.&lt;/strong&gt; You spend the whole game dismantling his operation, and the final badge, the one the guard is waiting for, gets handed to you by the villain himself.&lt;/p&gt;

&lt;p&gt;Kid-me thought that was a cool twist. Adult-me sees what the twist is actually saying. Think about what Team Rocket &lt;em&gt;is&lt;/em&gt;: they don't raise Pokémon, they steal them. Their entire philosophy is getting the power without doing the training. And Giovanni is living proof it works: he's rich, he's feared, he runs a whole city. The game puts him, of all people, between you and the League. The final obstacle isn't a stronger trainer. It's the successful guy offering you his way of doing things.&lt;/p&gt;

&lt;p&gt;That's why I call it a temptation. The final boss of agentic engineering isn't a gnarly bug. It's the shortcut philosophy, and it &lt;em&gt;visibly works&lt;/em&gt;, at least at first. Let the agent's judgment quietly replace yours. Ship volume because merging feels like progress. Paste 200 lines you didn't read because it looked right and it was late. Shipping unread agent output is stealing Pokémon instead of training them: same power on paper, none of it yours.&lt;/p&gt;

&lt;p&gt;And before you tell me this is a "skill issue" that only happens to juniors: the industry is drifting into it &lt;em&gt;at scale&lt;/em&gt;, and we have receipts. GitClear analyzed 211 million changed lines of code across five years. Duplicated code blocks jumped 8x in a single year. Copy/pasted lines exceeded refactored ("moved") lines for the first time in the dataset's history. Churn, the code you rewrite within two weeks of committing it because it wasn't right the first time, roughly doubled from its pre-AI baseline. And Google's own DORA report found delivery stability dropped 7.2% as AI adoption went up.&lt;/p&gt;

&lt;p&gt;You can watch the same movie at a single company. Spotify's VP of Engineering recently shared that they ship 4,500 production deploys a day, with 73% of PRs now AI-assisted. Genuinely impressive numbers. Meanwhile, their own community forums are an endless scroll of "the app gets slower and worse with every update," and the most viral reaction to those 4,500 deploys was someone asking what in the hell they're all &lt;em&gt;for&lt;/em&gt;, since the product barely feels different. I'm not saying the AI-assisted PRs caused the complaints, that would be lazy. I'm saying throughput went vertical and perceived quality didn't follow, and that's a problem.&lt;/p&gt;

&lt;p&gt;Sources, so you can check me. The 211M lines analysis:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://www.gitclear.com/ai_assistant_code_quality_2025_research" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;gitclear.com&lt;/span&gt;
          

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


&lt;p&gt;DORA on delivery stability:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://dora.dev/research/2024/dora-report/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdora.dev%2Fimg%2Fmisc%2Fdora_social_image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://dora.dev/research/2024/dora-report/" rel="noopener noreferrer" class="c-link"&gt;
            DORA | Accelerate State of DevOps Report 2024
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            DORA is a long running research program that seeks to understand the capabilities that drive software delivery and operations performance. DORA helps teams apply those capabilities, leading to better organizational performance.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdora.dev%2Ffavicon-32x32.png" width="39" height="39"&gt;
          dora.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Spotify's 4,500 deploys/day and 73% AI-assisted PRs + reaction:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-2071817938076860432-223" src="https://platform.twitter.com/embed/Tweet.html?id=2071817938076860432"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-2071817938076860432-223');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2071817938076860432&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Everyone is shipping faster. What's shipping is not clearly getting better. Nobody decided this. Millions of developers drifted into it, one skipped review at a time. That's what makes it the boss fight.&lt;/p&gt;

&lt;p&gt;The annoying fact that keeps you grounded: &lt;strong&gt;the agent doesn't own the PR. You do.&lt;/strong&gt; When it breaks, nobody opens a ticket against the model. Code is cheap now. Your name isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Eight badges. Now what?
&lt;/h2&gt;

&lt;p&gt;The gate isn't the end. It's a new beginning. As everything in life, once you reached the summit, you realize it was one of many...&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>career</category>
    </item>
    <item>
      <title>The Apple Books data model</title>
      <dc:creator>Kevin Alemán</dc:creator>
      <pubDate>Mon, 22 Jun 2026 19:54:21 +0000</pubDate>
      <link>https://dev.to/kaleman15/the-apple-books-data-model-1f8h</link>
      <guid>https://dev.to/kaleman15/the-apple-books-data-model-1f8h</guid>
      <description>&lt;p&gt;Apple Books stores everything about your reading on disk, in plain SQLite. Your books, your highlights, your shelves, your ratings, the dates you finished things, all of it sits in a handful of databases you can open and query right now. The app surfaces only a fraction of it.&lt;/p&gt;

&lt;p&gt;This is a reference for that data model: where it lives, the conventions it follows, the entities that matter, and, most importantly, how those entities connect. The relationships are the interesting part. A single table is just a list; the model is the set of links between tables, and Apple Books has three distinct relationship patterns worth understanding.&lt;/p&gt;

&lt;p&gt;Everything below is observable on any Mac with Books installed. No private APIs, no entitlements, just SQLite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage layout
&lt;/h2&gt;

&lt;p&gt;Sandboxed macOS apps keep their data under &lt;code&gt;~/Library/Containers&lt;/code&gt;. Apple Books spreads its across a few SQLite files in two containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/Library/Containers/com.apple.iBooksX/Data/Documents/
├── BKLibrary/
│   └── BKLibrary-1-091020131601.sqlite           # the library: one row per book
├── AEAnnotation/
│   └── AEAnnotation_v10312011_1727_local.sqlite  # highlights and notes
└── BCRecentlyOpenedBooksDB/
    └── BCRecentlyOpenedBooksDB.sqlite            # recently-opened events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The numeric suffixes in the filenames are not stable across installs, so match on the prefix (&lt;code&gt;BKLibrary-1-*.sqlite&lt;/code&gt;) rather than hardcoding the full name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Data conventions
&lt;/h2&gt;

&lt;p&gt;Every table and column is uppercase and prefixed with &lt;code&gt;Z&lt;/code&gt;: &lt;code&gt;ZBKLIBRARYASSET&lt;/code&gt;, &lt;code&gt;ZTITLE&lt;/code&gt;, &lt;code&gt;ZANNOTATIONASSETID&lt;/code&gt;. That prefix is the signature of &lt;strong&gt;Core Data&lt;/strong&gt;, Apple's persistence framework, which generates the SQLite schema underneath. Recognizing it tells you what to expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Z_PK&lt;/code&gt; — integer primary key on every entity table.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Z_ENT&lt;/code&gt; — which entity (table) a row belongs to.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Z_OPT&lt;/code&gt; — Core Data's optimistic-locking version counter.&lt;/li&gt;
&lt;li&gt;Application columns carry the &lt;code&gt;Z&lt;/code&gt; prefix too (&lt;code&gt;ZTITLE&lt;/code&gt;, &lt;code&gt;ZRATING&lt;/code&gt;, ...).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two quirks follow from this that affect almost every query:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Booleans are nullable.&lt;/strong&gt; Flags like &lt;code&gt;ZISFINISHED&lt;/code&gt; are &lt;code&gt;1&lt;/code&gt; or &lt;code&gt;NULL&lt;/code&gt;, not &lt;code&gt;1&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt;. &lt;code&gt;WHERE ZISFINISHED = 0&lt;/code&gt; matches nothing. Use &lt;code&gt;IS NULL&lt;/code&gt; / &lt;code&gt;!= 1&lt;/code&gt; for the negative case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timestamps use a 2001 epoch.&lt;/strong&gt; Core Data counts seconds from &lt;code&gt;2001-01-01&lt;/code&gt;, not the Unix &lt;code&gt;1970-01-01&lt;/code&gt;. The two differ by exactly &lt;code&gt;978307200&lt;/code&gt; seconds. To read any date column as a real date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ZDATEFINISHED&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;978307200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'unixepoch'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ZBKLIBRARYASSET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- 2026-05-16&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every date column in every one of these databases uses the same offset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the files safely
&lt;/h2&gt;

&lt;p&gt;Books keeps these databases open in &lt;strong&gt;WAL mode&lt;/strong&gt; (Write-Ahead Logging). Recent changes live in &lt;code&gt;-wal&lt;/code&gt; and &lt;code&gt;-shm&lt;/code&gt; sidecar files next to the main &lt;code&gt;.sqlite&lt;/code&gt; and have not yet been checkpointed into it. Opening the main file directly while Books is running yields stale reads or lock errors.&lt;/p&gt;

&lt;p&gt;The safe approach for read-only analysis is to copy the database &lt;strong&gt;and its sidecars&lt;/strong&gt; to a scratch location, then open the copy read-only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BKLibrary-1-...sqlite      →  copy
BKLibrary-1-...sqlite-wal  →  copy   (must travel together)
BKLibrary-1-...sqlite-shm  →  copy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLite reconstructs the current state from the copied set, and the originals are never touched.&lt;/p&gt;

&lt;h2&gt;
  
  
  The central entity: ZBKLIBRARYASSET
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ZBKLIBRARYASSET&lt;/code&gt; is the hub of the model: one row per item in your library, and the table nearly everything else links back to. It has over a hundred columns; these are the ones that carry meaning:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ZASSETID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;stable identifier for the book — the join key for the entire model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ZTITLE&lt;/code&gt;, &lt;code&gt;ZAUTHOR&lt;/code&gt;, &lt;code&gt;ZGENRE&lt;/code&gt;, &lt;code&gt;ZLANGUAGE&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;descriptive metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ZPAGECOUNT&lt;/code&gt;, &lt;code&gt;ZREADINGPROGRESS&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;length, and progress as a 0–1 float&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ZISFINISHED&lt;/code&gt;, &lt;code&gt;ZDATEFINISHED&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;completion flag (1/NULL) and date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ZRATING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;user star rating; &lt;code&gt;0&lt;/code&gt; means unrated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ZPURCHASEDATE&lt;/code&gt;, &lt;code&gt;ZLASTENGAGEDDATE&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;acquisition and last-interaction timestamps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ZCONTENTTYPE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;format/role of the row (see the series section)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ZSERIESID&lt;/code&gt;, &lt;code&gt;ZSERIESNEXTFLAG&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;series membership and "next up" marker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ZDURATION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;runtime in seconds for audiobooks (&lt;code&gt;0&lt;/code&gt; otherwise)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A baseline query, books finished:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&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="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ZBKLIBRARYASSET&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ZISFINISHED&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ZASSETID&lt;/code&gt; is the thread that runs through the whole model. Hold onto it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The relationships
&lt;/h2&gt;

&lt;p&gt;Three relationships connect to &lt;code&gt;ZBKLIBRARYASSET&lt;/code&gt;, and each uses a different structural pattern: a cross-database join, a junction table, and a self-join.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Highlights — a cross-database relationship
&lt;/h3&gt;

&lt;p&gt;Highlights and notes are not stored with the books. They live in a separate database, &lt;code&gt;AEAnnotation&lt;/code&gt;, in the table &lt;code&gt;ZAEANNOTATION&lt;/code&gt;. Each row is one annotation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ZANNOTATIONASSETID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;foreign key to the book&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ZANNOTATIONSELECTEDTEXT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the highlighted text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ZANNOTATIONNOTE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;attached note, if any&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ZANNOTATIONSTYLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;highlight color, &lt;code&gt;0&lt;/code&gt;–&lt;code&gt;5&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ZANNOTATIONDELETED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;soft-delete flag — filter &lt;code&gt;= 0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The link is &lt;code&gt;ZANNOTATIONASSETID&lt;/code&gt;, and the non-obvious detail is which library column it targets. It is &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;ZEPUBID&lt;/code&gt;; it matches &lt;code&gt;ZASSETID&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ZBKLIBRARYASSET.ZASSETID  ──1:N──  ZAEANNOTATION.ZANNOTATIONASSETID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the two tables live in different files, the join requires attaching one database to the other first. SQLite's &lt;code&gt;ATTACH&lt;/code&gt; makes them queryable as one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;ATTACH&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="s1"&gt;'annotations.sqlite'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ann&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZTITLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;count&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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;highlights&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ann&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZAEANNOTATION&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;ZBKLIBRARYASSET&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZASSETID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZANNOTATIONASSETID&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZANNOTATIONDELETED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZANNOTATIONSELECTEDTEXT&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZTITLE&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;highlights&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Become an Effective Software Engineering Manager | 404
La Atalaya, marzo de 2026                        |  53
The Midnight Library: A Novel                    |  25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Shelves — a many-to-many via junction table
&lt;/h3&gt;

&lt;p&gt;Collections (&lt;em&gt;Want to Read&lt;/em&gt;, &lt;em&gt;Finished&lt;/em&gt;, and custom shelves) are a many-to-many relationship: a book can belong to several collections, and a collection holds many books. Core Data models this with a junction table in the middle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ZBKCOLLECTION           ZBKCOLLECTIONMEMBER          ZBKLIBRARYASSET
  Z_PK   ◄──────────────  ZCOLLECTION                     ZASSETID
  ZTITLE                   ZASSETID  ─────────────────────────►
                           ZSORTKEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ZBKCOLLECTIONMEMBER&lt;/code&gt; holds one row per membership, pointing both ways: &lt;code&gt;ZCOLLECTION&lt;/code&gt; references the collection's &lt;code&gt;Z_PK&lt;/code&gt;, and &lt;code&gt;ZASSETID&lt;/code&gt; references the book. Listing the contents of a named shelf means traversing the junction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZTITLE&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ZBKCOLLECTIONMEMBER&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;ZBKCOLLECTION&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;   &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Z_PK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZCOLLECTION&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;ZBKLIBRARYASSET&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZASSETID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZASSETID&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZTITLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Want to Read'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZSORTKEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Series — a self-join with a container row
&lt;/h3&gt;

&lt;p&gt;Series have no dedicated table. Instead, two things encode them inside &lt;code&gt;ZBKLIBRARYASSET&lt;/code&gt; itself:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every book in a series carries a shared &lt;code&gt;ZSERIESID&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The series itself is a &lt;strong&gt;placeholder row in the same table&lt;/strong&gt;, identified by &lt;code&gt;ZCONTENTTYPE = 5&lt;/code&gt;. It stores the series' name and metadata but is not a readable book.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Reporting on a series therefore means joining &lt;code&gt;ZBKLIBRARYASSET&lt;/code&gt; to itself: the real members on one side, the container row on the other, matched on &lt;code&gt;ZSERIESID&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZTITLE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ZBKLIBRARYASSET&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
     &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZSERIESID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZSERIESID&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZCONTENTTYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;count&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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZISFINISHED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ZBKLIBRARYASSET&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZSERIESID&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZCONTENTTYPE&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZSERIESID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ZCONTENTTYPE != 5&lt;/code&gt; predicate matters beyond this query. The container row counts as a row in the table, so any aggregate over books, totals, format breakdowns, library size, must exclude type &lt;code&gt;5&lt;/code&gt; or it will overcount by one per series.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model at a glance
&lt;/h2&gt;

&lt;p&gt;The three relationships form one compact graph, all anchored on &lt;code&gt;ZASSETID&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌─────────────────────┐
   highlights ──────┤                     ├────── shelves
ZAEANNOTATION       │   ZBKLIBRARYASSET   │   ZBKCOLLECTION
(separate DB,       │      (ZASSETID)     │   via junction table
 ATTACH + JOIN)     │                     │   ZBKCOLLECTIONMEMBER
                    └──────────┬──────────┘
                               │ self-join on ZSERIESID
                               │ (container row, ZCONTENTTYPE = 5)
                            series
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One central entity, three patterns: a cross-database join for annotations, a junction table for collections, a self-join for series. Most questions about a reading library reduce to one of these traversals.&lt;/p&gt;

&lt;h2&gt;
  
  
  A documented limitation: reading sessions
&lt;/h2&gt;

&lt;p&gt;The third database, &lt;code&gt;BCRecentlyOpenedBooksDB&lt;/code&gt;, contains &lt;code&gt;ZBCASSETREADINGSESSION&lt;/code&gt;, with &lt;code&gt;ZTIMEOPENED&lt;/code&gt; and &lt;code&gt;ZTIMECLOSED&lt;/code&gt; columns that look like they record reading duration. They do not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;opened                | closed               | duration
2026-06-10 17:49:29   | 2026-06-10 17:49:33  | 4 seconds
2026-05-05 14:53:33   | 2026-05-05 14:53:56  | 22 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each session spans a few seconds (an open event, not a reading span), &lt;code&gt;ZTIMEUPDATED&lt;/code&gt; is empty, and only the most recent handful of rows are retained. Actual time-spent-reading is not persisted and cannot be reconstructed from this data. The open &lt;em&gt;timestamps&lt;/em&gt; are valid, so they can support an opened-on-this-day signal, but not minutes-read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Querying your own library
&lt;/h2&gt;

&lt;p&gt;None of this requires special tooling. On any Mac with Books:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sqlite3 ~/Library/Containers/com.apple.iBooksX/Data/Documents/BKLibrary/BKLibrary-1-&lt;span class="k"&gt;*&lt;/span&gt;.sqlite &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"SELECT ZTITLE, ZAUTHOR FROM ZBKLIBRARYASSET WHERE ZISFINISHED = 1;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, the rest of the model is reachable by following &lt;code&gt;ZASSETID&lt;/code&gt;: attach the annotation database for highlights, walk &lt;code&gt;ZBKCOLLECTIONMEMBER&lt;/code&gt; for shelves, self-join on &lt;code&gt;ZSERIESID&lt;/code&gt; for series. The recurring practicalities are the same throughout: recognize the Core Data prefix, add &lt;code&gt;978307200&lt;/code&gt; to every timestamp, treat boolean flags as nullable, and exclude &lt;code&gt;ZCONTENTTYPE = 5&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix: a reference implementation
&lt;/h2&gt;

&lt;p&gt;To exercise the model end to end, I built &lt;strong&gt;ReadStats&lt;/strong&gt;, a small macOS menu-bar app that reads these databases (read-only, via the copy-with-sidecars approach above) and renders the stats they imply: finished-per-year, currently-reading progress, the want-to-read backlog, stalled books, highlight counts and colors, series progress, and a searchable browser over every highlight.&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%2F7pllx579pg4aahf4rumq.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%2F7pllx579pg4aahf4rumq.png" alt="ReadStats main view" width="360" height="573"&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%2Feof6m0uc7nxoh61820jf.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%2Feof6m0uc7nxoh61820jf.png" alt="ReadStats library view" width="634" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each view in it is, underneath, one of the queries from this document. The library catalog is &lt;code&gt;ZBKLIBRARYASSET&lt;/code&gt;; the "most highlighted" list is the cross-database join; the backlog is the junction-table traversal; the series card is the self-join. The data model did the hard part. The app is mostly presentation on top of these relationships.&lt;/p&gt;

&lt;p&gt;If it's useful as a worked example of mapping a Core Data SQLite schema into a typed domain model, that's the part worth borrowing.&lt;/p&gt;

&lt;p&gt;The official disclaimer is that the whole discovery was assisted by Claude. Pretty fun stuff checking the databases and how they connect to each other and being able to re-present the information in a nice way.&lt;/p&gt;

</description>
      <category>macos</category>
      <category>sqlite</category>
      <category>programming</category>
      <category>database</category>
    </item>
    <item>
      <title>What are your most liked agent skills?</title>
      <dc:creator>Kevin Alemán</dc:creator>
      <pubDate>Fri, 19 Jun 2026 14:41:59 +0000</pubDate>
      <link>https://dev.to/kaleman15/what-are-your-most-liked-agent-skills-1d0f</link>
      <guid>https://dev.to/kaleman15/what-are-your-most-liked-agent-skills-1d0f</guid>
      <description>&lt;p&gt;An space for sharing your go-to agent skills so everyone can use them.&lt;/p&gt;

&lt;p&gt;My list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Superpowers (planning &amp;amp; implementation)&lt;/li&gt;
&lt;li&gt;Caveman (for reducing token usage)&lt;/li&gt;
&lt;li&gt;Frontend Design (to help with styling)&lt;/li&gt;
&lt;li&gt;Remember (memory)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have one i created myself called &lt;code&gt;teachme&lt;/code&gt;, which uses a bit of thinking to explain topics to you and then it evaluates your understanding.&lt;/p&gt;

&lt;p&gt;:)&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Gotta Earn 'Em All: The Gym Badges of Agentic Engineering (Part 1)</title>
      <dc:creator>Kevin Alemán</dc:creator>
      <pubDate>Wed, 17 Jun 2026 20:13:57 +0000</pubDate>
      <link>https://dev.to/kaleman15/gotta-earn-em-all-the-gym-badges-of-agentic-engineering-part-1-5bff</link>
      <guid>https://dev.to/kaleman15/gotta-earn-em-all-the-gym-badges-of-agentic-engineering-part-1-5bff</guid>
      <description>&lt;p&gt;There's a guy who stands at the entrance to the Indigo Plateau and will not let you through. Level 80 Charizard? Doesn't care. Seven gym badges pinned to your jacket? Come back when it's eight. No matter what you do, he won't let you pass until you meet the prerequisites.&lt;/p&gt;

&lt;p&gt;Kid-me thought he was the single most pointless NPC ever programmed. I wanted to go right to the action, not spending 5 hours to get all the badges. Replaying FireRed last month (the GOAT, don't @ me) I finally got what he's for. He isn't guarding the fun part. He's making sure that by the time you face the four people who can actually beat you, you've already survived the eight boring things that made you ready for them.&lt;/p&gt;

&lt;p&gt;Which is, more or less, my entire complaint about how we're using agents right now.&lt;/p&gt;

&lt;p&gt;Everybody wants to fight the Elite Four on day one. "I'll let the agent build the whole feature." "I'll let it refactor the repo while I grab a coffee." "I'll point Claude Code at the thing and vibe." And sometimes it works! Right up until it doesn't, and you genuinely cannot say &lt;em&gt;why&lt;/em&gt; because there were eight gyms between you and that gate and you strolled past every one.&lt;/p&gt;

&lt;p&gt;So here's the case, the way a Game Boy made it to me: before you're actually good with agents, there are badges to earn. Not tools to install, &lt;em&gt;things you have to know.&lt;/em&gt; Let's go gym by gym.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Badge 1: Boulder Badge: rock before copilot
&lt;/h2&gt;

&lt;p&gt;Brock. Rock type. The first gym. The place where you thought choosing charmander was a bad call (until you trained enough and it learned Metal Claw which helps a little bit).&lt;/p&gt;

&lt;p&gt;This is the badge of knowing how to code &lt;em&gt;without&lt;/em&gt; the agent holding your hand. The foundations.&lt;/p&gt;

&lt;p&gt;I've said this before and I'll keep saying it until they revoke my keyboard: &lt;strong&gt;you cannot prompt what you don't know exists.&lt;/strong&gt; If you don't know your project just changed a function's typings, you won't tell the agent, you won't catch it when it gets it wrong, and you'll merrily ship something that &lt;em&gt;looks&lt;/em&gt; right and isn't. The agent didn't fail there. You showed up to the gym with no Pokémon.&lt;/p&gt;

&lt;p&gt;You don't need to know &lt;em&gt;everything&lt;/em&gt;. You need bedrock. Otherwise every badge after this one is built on sand and you're gonna have a bad time.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Badge 2: Cascade Badge: the agent only knows what you pour in
&lt;/h2&gt;

&lt;p&gt;Water flows, context flows, you see where I'm going. An agent isn't psychic (we'll get to that gym), it knows &lt;em&gt;exactly&lt;/em&gt; what's in its context window and nothing else. Not your repo's weird conventions. Not the thing your team decided in Slack last Tuesday. Not the landmine that lives rent-free in one senior dev's brain.&lt;/p&gt;

&lt;p&gt;Look at these two and tell me they're getting the same result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// what most people send
"fix the bug in the user evaluation"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// what you could be doing
"In src/users/evaluate.ts we hardcoded a company name to hit a deadline.
Now we ALSO need to exclude new users from company "bla",
WITHOUT touching how "fancy company" users get evaluated.
Here's the file, here's the test that has to stay green,
here's how we name things."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same model. The first one is basically throwing a Poké Ball at a wall. The second one is, you know, &lt;em&gt;engineering.&lt;/em&gt; Pour the context on purpose and everything downstream gets less cursed.&lt;/p&gt;

&lt;p&gt;Don't make your agent drink from a puddle.&lt;/p&gt;

&lt;p&gt;There's some cool skills that can make your agent "think" this way, but you have to think that way too, so both agent &amp;amp; you can work together on solving &amp;amp; creating.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Badge 3: Thunder Badge: fast is a trap
&lt;/h2&gt;

&lt;p&gt;Here's what nobody warns you about: agents are &lt;em&gt;fast.&lt;/em&gt; Stupid fast. Faster than you. Faster than me. Faster than anything likely. Issues that would take me a week to solve an agent can find a probable cause in minutes. And speed multiplies whatever you give it, your galaxy-brain ideas and your dumbest ones, at the exact same voltage.&lt;/p&gt;

&lt;p&gt;I've ranted about this in open-source land: opening a PR now costs basically nothing, but &lt;em&gt;reviewing&lt;/em&gt; one still costs a human their actual time and sanity. That gap is a denial-of-service attack wearing a trenchcoat. You tell the agent "do this for every file!!" and now there are 100 changes, some great, some radioactive, and you have to read all 100 because the agent will not tell you which is which. Congrats, you DoS'd yourself.&lt;/p&gt;

&lt;p&gt;The badge here is learning to &lt;em&gt;not grab the lightning with both hands.&lt;/em&gt; Small tasks. Bounded scope. One thing at a time. A fast wrong answer isn't a head start, it just got to being wrong sooner. :(&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Badge 4: Rainbow Badge: it's gardening, not a vending machine
&lt;/h2&gt;

&lt;p&gt;People treat agents like a vending machine: insert prompt, slot opens, finished feature falls out. Nope. It's &lt;em&gt;gardening.&lt;/em&gt; You plant a thing, you look at the weird mutant that grew, you prune the cursed branches, you re-prompt, you prune again. Repeat until it's a plant and not a crime scene.&lt;/p&gt;

&lt;p&gt;The first output is never the deliverable. It's a sprout. "Good, keep this shape, but the data layer is wrong, redo that part" is the actual loop. The people who look like wizards with agents aren't great prompters, they're patient gardeners who aren't precious about yeeting a bad draft into the sun.&lt;/p&gt;

&lt;p&gt;Plant small, prune often, be responsible for what your agent produces cause that's your personal image too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Halfway to the League
&lt;/h2&gt;

&lt;p&gt;Four badges down. If you actually have these: bedrock, context, scoped speed, and the patience to garden instead of treating the agent like a vending machine, you're already ahead of most people loudly pretending to be good at this.&lt;/p&gt;

&lt;p&gt;Learning is a journey. Enjoy it. Don't let agents &amp;amp; AI remove from you the better part of programming: you getting better at it.&lt;/p&gt;

&lt;p&gt;Obviously, life is not Pokemon. You could perfectly bypass all of this and go straight into your terminal right now to get a "product". That's acceptable in some cases.&lt;/p&gt;

&lt;p&gt;But... where's the fun in that?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>career</category>
    </item>
    <item>
      <title>You got selected for GSoC 2026, now what?</title>
      <dc:creator>Kevin Alemán</dc:creator>
      <pubDate>Mon, 04 May 2026 04:35:06 +0000</pubDate>
      <link>https://dev.to/kaleman15/you-got-selected-for-gsoc-2026-now-what-1bdc</link>
      <guid>https://dev.to/kaleman15/you-got-selected-for-gsoc-2026-now-what-1bdc</guid>
      <description>&lt;p&gt;So your inbox lit up yesterday with the email. The proposal worked, the interviews worked, the late-night drafts worked. Take a moment, breathe, tell your family, post the screenshot. You earned it 🎉&lt;br&gt;
Now, the actual fun part begins.&lt;/p&gt;

&lt;p&gt;GSoC is, at its core, a few months of getting paid to learn from people who have spent years figuring out how to build software that thousands (sometimes millions) of strangers depend on. Think of it as an internship, but instead of a single company, your "office" is the entire open source world, and your "coworkers" are engineers who chose to spend their free time making things better for everyone. That's a rare seat at the table. Don't waste it.&lt;/p&gt;

&lt;p&gt;Here are a few things to keep in mind so you make the most of it.&lt;/p&gt;
&lt;h2&gt;
  
  
  What a mentor looks for from you
&lt;/h2&gt;
&lt;h3&gt;
  
  
  They want to mentor, not babysit.
&lt;/h3&gt;

&lt;p&gt;Mentors signed up to guide you, answer the hard questions, and unblock you when the codebase fights back. What they did not sign up for is reading the README on your behalf. The heavy lifting, exploring the codebase, running it locally, breaking it, fixing it, writing code that smells like the rest of the repo, that's on you. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A good rule: if you can find the answer in 30 minutes of digging, dig. If you've dug for two hours and you're more confused than when you started, ping them.&lt;/p&gt;

&lt;p&gt;Note 2: at Rocket.Chat, we have an internal meme called &lt;code&gt;:call-the-police:&lt;/code&gt; that would fit perfectly above. Sadly, due to GDPR and other international laws, I cannot share above. End of the note.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Honesty is everything.
&lt;/h3&gt;

&lt;p&gt;LLMs are great at giving you &lt;em&gt;an answer&lt;/em&gt;. They're not great at giving you the answer for this specific repo, with this specific history, with that one weird workaround that's there because of a bug from 2019 nobody wants to revisit. When you step into that kind of fog, just say so. &lt;strong&gt;Mentors respect "I don't get why this is here" way more than a confident wrong guess&lt;/strong&gt;. That said, don't ask for permission to breathe. There's a middle ground between "I'm stuck on every line" and "I haven't said anything in two weeks", and that's where you want to live.&lt;/p&gt;
&lt;h3&gt;
  
  
  Let git do some of the talking.
&lt;/h3&gt;

&lt;p&gt;In a lot of open source repos, the git history is the real diary of the project. Match the style: if they squash, you squash; if they write essays in commit messages, you do too. &lt;strong&gt;Use commits as a steady drumbeat of progress, and save your mentor's inbox for the moments that actually need a human&lt;/strong&gt;. Adding a translation? That's a commit. Untangling a nasty bug and finding a creative fix? That's worth a "hey, look at this" message.&lt;/p&gt;
&lt;h2&gt;
  
  
  What you can ask from your mentor
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Treat them like human documentation.
&lt;/h3&gt;

&lt;p&gt;Mentors are walking archives. They know why a function is named weirdly, which refactor everyone is afraid to touch, and that one PR from 2021 that explains everything. Tap into that, but don't drain the well. Try to find the answer yourself first, give it a responsible amount of time, and if you're still spinning, ask. &lt;strong&gt;The deadline is fixed&lt;/strong&gt;, and running in circles is the most expensive thing you can do with your summer.&lt;/p&gt;
&lt;h3&gt;
  
  
  Ask about the politics the proposal couldn't see.
&lt;/h3&gt;

&lt;p&gt;When you wrote the proposal, you were looking at the project from the outside. Now you're inside, and you'll start spotting things: tech debt, weird coupling, decisions that look wrong until someone explains they're load-bearing. Bring those up. Sometimes the answer will be "yes, let's fix it as part of your work." Other times it'll be "leave it, that rabbit hole eats summers." &lt;em&gt;Both are useful answers, and only your mentor can give them to you.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How to deliver on the proposal you already submitted
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The proposal is the map, not the territory.
&lt;/h3&gt;

&lt;p&gt;You can't redraw it: the timeline, the deliverables, the headline goals are pretty much locked. But there's plenty of room inside the lines: code style, data shapes, security choices, edge cases, testing strategy. &lt;strong&gt;Talk those out with your mentor early&lt;/strong&gt;. The earlier the better, because rework in week 10 hurts a lot more than rework in week 2.&lt;/p&gt;
&lt;h3&gt;
  
  
  Once you've aligned, write a plan.
&lt;/h3&gt;

&lt;p&gt;Break the proposal into weekly chunks, with a rough idea of what "done" looks like for each one. Then, and this is the important part, don't die by the plan. &lt;strong&gt;Plans that are too tight have no room to absorb the inevitable&lt;/strong&gt;: a flu, a flaky test, a rabbit hole, that one dependency that decides to deprecate itself the week you need it. &lt;em&gt;If something can go wrong, it will go wrong&lt;/em&gt;. Plan for it.&lt;/p&gt;


&lt;div class="ltag__wikipedia--container"&gt;
  &lt;div class="ltag__wikipedia--header"&gt;
    &lt;img src="https://assets.dev.to/assets/wikipedia-logo-0a3e76624c7b1c3ccdeb9493ea4add6ef5bd82d7e88d102d5ddfd7c981efa2e7.svg" class="ltag__wikipedia--logo" alt="Wikipedia Logo" width="128" height="128"&gt;
    &lt;a href="https://en.wikipedia.org/wiki/Murphy%27s_law" rel="noopener noreferrer"&gt;Murphy's law&lt;/a&gt;
  &lt;/div&gt;
  &lt;div class="ltag__wikipedia--extract"&gt;&lt;p&gt;&lt;b&gt;Murphy's law&lt;/b&gt; is an adage or epigram that is typically stated as: "Anything that can go wrong &lt;i&gt;will&lt;/i&gt; go wrong."&lt;/p&gt;&lt;/div&gt;
  &lt;div class="ltag__wikipedia--btn--container"&gt;
      &lt;a class="ltag__wikipedia--btn" href="https://en.wikipedia.org/wiki/Murphy%27s_law" rel="noopener noreferrer"&gt;View on Wikipedia&lt;/a&gt;&amp;gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  When to use AI (and when to put your hands on the keyboard)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use AI to plan, not to know.
&lt;/h3&gt;

&lt;p&gt;AI is genuinely great for getting your bearings in a new codebase: "what does this module do", "where is X handled", "give me a tour of this folder". The trap is that it'll happily give you confident answers that feel like understanding without actually being understanding. Always review the plan it gives you. Always question its assumptions. &lt;strong&gt;At the end of the summer, nobody is evaluating the LLM. They're evaluating you&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't let AI eat your learning.
&lt;/h3&gt;

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

&lt;p&gt;This is the part I want you to take most seriously. GSoC is one of the safest spaces you'll ever get to make mistakes: you have a mentor, a stipend, and explicit permission to be a beginner. If you outsource the thinking to an LLM, you're trading the most valuable thing about the program for some saved keystrokes. Especially when it comes to understanding issues: sit with the problem first. Read the code. Form your own theory. Then maybe ask the AI for a second opinion. &lt;strong&gt;AI already "knows" a lot of stuff. The whole point of the summer is: what do you know?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Balance check: we don't want you to feel we (or I) hate AI. AI is really cool and I'm very happy on using it. But as prof. Oak liked to say: "There's a place and time for everything". AI is cool, but the human using it is cooler.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Maintainers can smell it.
&lt;/h3&gt;

&lt;p&gt;I've reviewed enough PRs to tell you this isn't a cute claim: it's true. Code that's 100% AI and 0% human reads differently. The variable names are too generic, the comments explain things nobody would explain, the solution is technically correct but doesn't fit the shape of the codebase, the PR title and/or the code make no sense. Mentors will spot it. And we, the mentors, want you to learn, that's the whole point of GSoC. We want more maintainers for the future. You can be one of them if you use this opportunity.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;You'll quickly realize getting selected was the easiest part. &lt;strong&gt;This summer is the real thing&lt;/strong&gt;. Drive your own work, ask the smart questions, ship the small stuff, keep AI as a tool and not as a crutch and you'll come out the other side a much better engineer than the one who opened that email yesterday.&lt;/p&gt;

&lt;p&gt;See you at the final eval 👋&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>gsoc</category>
      <category>software</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Got featured :noice:</title>
      <dc:creator>Kevin Alemán</dc:creator>
      <pubDate>Tue, 10 Mar 2026 16:45:59 +0000</pubDate>
      <link>https://dev.to/kaleman15/-i0</link>
      <guid>https://dev.to/kaleman15/-i0</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/devteam/top-7-featured-dev-posts-of-the-week-1889" class="crayons-story__hidden-navigation-link"&gt;Top 7 Featured DEV Posts of the Week&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/devteam"&gt;
            &lt;img alt="The DEV Team logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1%2Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/jess" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F264%2Fb75f6edf-df7b-406e-a56b-43facafb352c.jpg" alt="jess profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/jess" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Jess Lee
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Jess Lee
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3331226" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/jess" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F264%2Fb75f6edf-df7b-406e-a56b-43facafb352c.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Jess Lee&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/devteam" class="crayons-story__secondary fw-medium"&gt;The DEV Team&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/devteam/top-7-featured-dev-posts-of-the-week-1889" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 10&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/devteam/top-7-featured-dev-posts-of-the-week-1889" id="article-link-3331226"&gt;
          Top 7 Featured DEV Posts of the Week
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/discuss"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;discuss&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/top7"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;top7&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/devteam/top-7-featured-dev-posts-of-the-week-1889" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;47&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/devteam/top-7-featured-dev-posts-of-the-week-1889#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              18&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>top7</category>
      <category>discuss</category>
    </item>
    <item>
      <title>🙌🏽</title>
      <dc:creator>Kevin Alemán</dc:creator>
      <pubDate>Sun, 08 Mar 2026 18:51:54 +0000</pubDate>
      <link>https://dev.to/kaleman15/-j59</link>
      <guid>https://dev.to/kaleman15/-j59</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/kaleman15/in-the-ai-era-code-is-cheap-reputation-isnt-3482" class="crayons-story__hidden-navigation-link"&gt;In the AI Era, Code Is Cheap. Reputation Isn’t.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/kaleman15" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F255126%2F6bdaeb84-6c1e-4e76-9d45-54c59eaac908.jpeg" alt="kaleman15 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/kaleman15" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Kevin Alemán
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Kevin Alemán
                
              
              &lt;div id="story-author-preview-content-3309486" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/kaleman15" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F255126%2F6bdaeb84-6c1e-4e76-9d45-54c59eaac908.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Kevin Alemán&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/kaleman15/in-the-ai-era-code-is-cheap-reputation-isnt-3482" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 4&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/kaleman15/in-the-ai-era-code-is-cheap-reputation-isnt-3482" id="article-link-3309486"&gt;
          In the AI Era, Code Is Cheap. Reputation Isn’t.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/software"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;software&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/kaleman15/in-the-ai-era-code-is-cheap-reputation-isnt-3482" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;31&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/kaleman15/in-the-ai-era-code-is-cheap-reputation-isnt-3482#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              17&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>opensource</category>
      <category>programming</category>
      <category>software</category>
    </item>
    <item>
      <title>In the AI Era, Code Is Cheap. Reputation Isn’t.</title>
      <dc:creator>Kevin Alemán</dc:creator>
      <pubDate>Wed, 04 Mar 2026 21:01:29 +0000</pubDate>
      <link>https://dev.to/kaleman15/in-the-ai-era-code-is-cheap-reputation-isnt-3482</link>
      <guid>https://dev.to/kaleman15/in-the-ai-era-code-is-cheap-reputation-isnt-3482</guid>
      <description>&lt;p&gt;In the era of AI it's easier than ever to be an Open Source contributor!&lt;/p&gt;

&lt;p&gt;But, at the same time, and quite paradoxically, it's harder than ever.&lt;/p&gt;

&lt;p&gt;Why? Because it's now &lt;em&gt;mechanically easier, but reputationally harder&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Previously, you competed against other incredibly smart volunteers, or maybe against employed developers that contributed their expertise and free time to open source, or against newcomers. Now, you compete against all of that and an army of AI agents generating pull requests, issues, refactors and vulnerability reports faster than what any human can do.&lt;/p&gt;

&lt;p&gt;It's pretty tempting. Using an LLM can reduce significantly the cognitive work you have to do to understand a codebase, and even to produce a valuable change request. Things that were previously "very hard" to do are now a few prompts away, and that lowered the entry barrier for contributions, which is great! But also created a massive increase in volume, which is flooding repositories.&lt;/p&gt;

&lt;p&gt;What's the problem? &lt;br&gt;
&lt;em&gt;Maintainer capacity did not increase&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Review cost did not decrease&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These two truths have already led maintainer to resort to drastic measures, like what happened with &lt;code&gt;curl&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://daniel.haxx.se/blog/2025/07/14/death-by-a-thousand-slops/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdaniel.haxx.se%2Fblog%2Fwp-content%2Fuploads%2F2017%2F09%2Fbug-insect-1200x803.jpg" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://daniel.haxx.se/blog/2025/07/14/death-by-a-thousand-slops/" rel="noopener noreferrer" class="c-link"&gt;
            Death by a thousand slops | daniel.haxx.se
          &lt;/a&gt;
        &lt;/h2&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdaniel.haxx.se%2Fblog%2Fwp-content%2Fuploads%2F2024%2F07%2Fdaniel-greenbg-blackandwhite-413x413-1.jpg"&gt;
          daniel.haxx.se
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;So, how do you become a valuable Open Source contributor in the era of infinite code generation? Here are a few tips learned from dealing with a large volume of contributions:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Think. Then think again
&lt;/h2&gt;

&lt;p&gt;AI can produce great code, but it can also produce the worst code ever seen by the humanity. This depends a lot on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;prompt&lt;/em&gt; that you use&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;quality&lt;/em&gt; of the LLM&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;context&lt;/em&gt; being shared&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the most important one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your own coding abilities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why do you need to know how to code before using LLMs to contribute to open source?&lt;/p&gt;

&lt;p&gt;Well, &lt;em&gt;because an LLM cannot know everything.&lt;/em&gt;&lt;br&gt;
For example, we had a PR that attempted to change this:&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Some error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;After&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Some error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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 would say: that is a valid change, we want to use &lt;code&gt;logger&lt;/code&gt; instead of &lt;code&gt;console&lt;/code&gt;! What's the problem?&lt;/p&gt;

&lt;p&gt;The problem was that the LLM didn't know (and didn't check, cause it wasn't asked to) that we recently changed the typings on our &lt;code&gt;logger&lt;/code&gt; instances, rendering the change invalid.&lt;/p&gt;

&lt;p&gt;What a valid change would have looked like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Some error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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;But, the LLM didn't know this, and it would have required quite some context for it to know. Good LLMs (Claude, Codex, etc) would have caught, but others would not, unless you asked them. How would you ask something that you didn't know? There's where your coding abilities enter into play.&lt;/p&gt;

&lt;p&gt;Here's the key point: &lt;em&gt;You cannot prompt what you don't know exists&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Someone that wants to be a contributor doesn't trust blindly on what the LLM produces. Knows how to use the tooling (typecheck, in this case) to catch those issues, reads the code to understand current conventions, caveats, etc.&lt;br&gt;
For this programmers, &lt;em&gt;LLMs become a powerful tool&lt;/em&gt;, cause it helps you with the mechanical task of coding and gives you enough time to think.&lt;/p&gt;

&lt;p&gt;This also protects your reputation. If maintainers see a pattern of low-signal PRs from you, they’ll eventually stop reviewing them. Or worse.&lt;/p&gt;

&lt;p&gt;LLMs make contribution faster, but they don’t replace your brain unless you let them.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Respect volume. Review is not free.
&lt;/h2&gt;

&lt;p&gt;You are happy. We're also happy that you want to contribute to open source. It's a noble endeavor and it's something that helps the repository to be safer &amp;amp; you to be better. It's a win-win!&lt;/p&gt;

&lt;p&gt;But, maintainers have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited capacity. &lt;/li&gt;
&lt;li&gt;Limited time. &lt;/li&gt;
&lt;li&gt;&lt;em&gt;Limited patience.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We understand, you are happy you created your new PR, and we understand the joy in you of doing that. We've all being there. You want, for sure, your PR to be merged so you can say with honor: "I contributed to X!"&lt;/p&gt;

&lt;p&gt;But, be reasonable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cost of opening a PR is now near zero.&lt;/li&gt;
&lt;li&gt;The cost of &lt;em&gt;reviewing&lt;/em&gt; it is not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This asymmetry is the real tension of the AI era.&lt;/p&gt;

&lt;p&gt;If you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;comment “please review” repeatedly,&lt;/li&gt;
&lt;li&gt;ping maintainers aggressively,&lt;/li&gt;
&lt;li&gt;demand merging timelines,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’re increasing the cognitive cost of your contribution. Even if your PR is valid.&lt;/p&gt;

&lt;p&gt;And here’s the harsh truth:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Maintainers are human.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If interacting with you feels expensive, they may subconsciously deprioritize your work. That means fewer reviews, fewer merges, fewer release notes mentions.&lt;/p&gt;

&lt;p&gt;This is specially important when you use LLMs or Agents as they are fast, way faster than humans, so you could end up doing 100 PRs that you think are valid, and maybe they are! But if the maintainers don't have the capacity to review them all, they just won't.&lt;/p&gt;

&lt;p&gt;That's the definition of a DoS (Denial of Service): you send more requests than what the server (maintainers) can process.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Quality that doesn't cut corners (pls don't sue me Wendy's)
&lt;/h2&gt;

&lt;p&gt;Maintainer capacity is low. We closed around 700 issues (stale, invalid, fixed) in the past month, but at the same time, we received 300 new issues. Some of those issues were valid, some of them were not. The problem is we don't know beforehand, &lt;em&gt;we have to check them all&lt;/em&gt;. You cannot trust an LLM to confirm if something is slop... cause the check may be slop too!&lt;/p&gt;

&lt;p&gt;Related link, &lt;code&gt;curl&lt;/code&gt; fighting against AI slop vulnerability reports:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hackerone.com/reports/3516186" rel="noopener noreferrer"&gt;HackerOne Curl report&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you see, maintainers won't be happy if you waste their time, cause they have stuff to do. The link above, as well as this beautiful list&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;





&lt;p&gt;have something in common: they look legit, until they don't. However, for realising something is slop you have to check manually, and that is time/effort wasted.&lt;/p&gt;

&lt;p&gt;So you have to be human, respect the times maintainers have and respect their work. How you do that? &lt;em&gt;Helping the maintainers reduce the cost of reviewing your code&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;Before submitting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did you reproduce the issue yourself?&lt;/li&gt;
&lt;li&gt;Did you run CI locally?&lt;/li&gt;
&lt;li&gt;Did you include screenshots for UI changes?&lt;/li&gt;
&lt;li&gt;Did you provide benchmarks for performance claims?&lt;/li&gt;
&lt;li&gt;Did you include a PoC for security reports?&lt;/li&gt;
&lt;li&gt;Did you check if a feature request already exists?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you claim a performance improvement, &lt;em&gt;show numbers&lt;/em&gt;.&lt;br&gt;
If you fix a bug, &lt;em&gt;explain the root cause&lt;/em&gt;.&lt;br&gt;
If you propose a feature, &lt;em&gt;ask first&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;High quality contributions get reviewed faster.&lt;br&gt;
Low signal contributions accumulate.&lt;/p&gt;

&lt;p&gt;In the AI era, &lt;em&gt;reputation compounds&lt;/em&gt;. If maintainers can trust you, your PRs will be reviewed faster, and you will feel happier. Win-win!&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Bundle trivial changes.
&lt;/h2&gt;

&lt;p&gt;Typos happen. Smol mistakes happen. For example &lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subsciption not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subscription not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Do you spot the bug? Yep, a missing &lt;code&gt;r&lt;/code&gt; on &lt;code&gt;subscription&lt;/code&gt;. The contribution is valid, yes, we want to write correct words in english.&lt;/p&gt;

&lt;p&gt;Does it deserve a PR? Not by itself, maybe if you combine a few more. Think if you find 10 of those misspellings. What's easier for maintainers? 1 PR with 10 changes or 10 PRs with 1 change? Hehe.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you were the maintainer, what would you prefer?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every PR has overhead: CI runs, notifications, review time, merge time, mental context switch, etc. Reduce the overhead by batching trivial work.&lt;/p&gt;

&lt;p&gt;AI can find an enormous list of fixes like this in a codebase, and if you use an agent like Claude or Clawd you can tell "Hey for each issue create a PR" and then become the most hated contributor on the project :(&lt;/p&gt;

&lt;p&gt;Don't do that. Please. Be mindful. &lt;em&gt;Be human&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  LLMs are fun, but human work is better.
&lt;/h2&gt;

&lt;p&gt;We're on the AI era, we cannot fight AIs anymore, they're here to stay. What we can fight is how we use them.&lt;/p&gt;

&lt;p&gt;For GSoC for example, we value human contributions. That's the point of the program: more humans converting to open source contributors!&lt;/p&gt;

&lt;p&gt;We need new generations of maintainers, of smart people that will use LLMs wisely. You can do amazing stuff with them if you use them properly, but nothing will replace your ability to do amazing stuff as a human. You have to remember that.&lt;/p&gt;

&lt;p&gt;It's easier to contribute, but harder to stand out from the crowd.&lt;br&gt;
Reputation matters.&lt;br&gt;
Trust is earned.&lt;br&gt;
You build it by doing good work.&lt;/p&gt;

&lt;p&gt;As a final note, this is pretty much personal experience. Some projects may have different rules. Some may like to receive 1000 PRs a day (?) idk.&lt;/p&gt;

&lt;p&gt;But, the main point is: AI is not human, but maintainers and contributors are. So remember that.&lt;/p&gt;

&lt;p&gt;Welcome to Open Source!&lt;/p&gt;

&lt;p&gt;If u want to read more:&lt;br&gt;


&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.jeffgeerling.com/blog/2026/ai-is-destroying-open-source/" rel="noopener noreferrer" class="c-link"&gt;
            AI is destroying Open Source, and it's not even good yet - Jeff Geerling
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Over the weekend Ars Technica retracted an article because the AI a writer used hallucinated quotes from an open source library maintainer.
The irony here is the maintainer in question, Scott Shambaugh, was harassed by someone's AI agent over not merging its AI slop code.
It's likely the bot was running through someone's local 'agentic AI' instance (likely using OpenClaw). The guy who built OpenClaw was just hired by OpenAI to "work on bringing agents to everyone." You'll have to forgive me if I'm not enthusastic about that.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          jeffgeerling.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;





</description>
      <category>ai</category>
      <category>opensource</category>
      <category>programming</category>
      <category>software</category>
    </item>
    <item>
      <title>You're doing great, and you need to hear that :)</title>
      <dc:creator>Kevin Alemán</dc:creator>
      <pubDate>Wed, 15 Sep 2021 16:09:03 +0000</pubDate>
      <link>https://dev.to/kaleman15/you-re-doing-great-and-you-need-to-hear-that-12ba</link>
      <guid>https://dev.to/kaleman15/you-re-doing-great-and-you-need-to-hear-that-12ba</guid>
      <description>&lt;p&gt;I'm not the kind of person that likes to be "thanked" for something. I like to do the things, and that's it. And later I realized it was not only me who felt this way. But, is receiving recognition something reserved for super special situations?&lt;/p&gt;

&lt;h2&gt;
  
  
  Everyone needs to be "praised" for doing a great work
&lt;/h2&gt;

&lt;p&gt;Being praised releases dopamine, that's why you feel happy when someone recognizes your work and says that it's good. Everyone likes that feeling of "doing something amazing" by receiving a simple "thanks for helping me". But, it doesn't stop there.&lt;/p&gt;

&lt;p&gt;Your motivation is also increased. It's important to note that everyone has a different set of motivators, so what may work for someone probably won't have the same effect for others. But there are some common motivators in a workplace, like the compensation. Receiving a considerable wage and a good compensation plan on your company may motivate you to work better. Same as having a clean workstation, or having a good team composed of capable people. Recognition is also a &lt;a href="https://www.thesuccessfactory.co.uk/blog/motivators-in-the-workplace-that-improve-employee-experience" rel="noopener noreferrer"&gt;key motivator&lt;/a&gt;, that means, surprisingly, that &lt;a href="https://www.trainingjournal.com/articles/feature/power-praise-and-recognition" rel="noopener noreferrer"&gt;people feel good&lt;/a&gt; when they're recognized :)&lt;/p&gt;

&lt;p&gt;However, as any other dopamine-releaser, the effect of recognition is transient, and, it may decrese the effect over time when it's done &lt;em&gt;too much&lt;/em&gt;. So, recognition should be given timely.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Timely recognition
&lt;/h2&gt;

&lt;p&gt;So, how do we achieve a recognition scheme that's not sporadic but is not rare at the same time? Balance, as always, is the key.&lt;/p&gt;

&lt;p&gt;This is more an HR topic, probably at your company there's a lot of people trying to fix this same issue, because recognition is not as easy as it may look. Fairness plays a &lt;em&gt;really&lt;/em&gt; important role. What if another employee who does the same thing I do receives recognition every week, but I receive it every year? Then, recognition becomes a problem and a demotivator, so it's important to watch out how recognition will be conducted.&lt;/p&gt;

&lt;p&gt;But, let's return to the frequency. From this &lt;a href="https://www.businessinsider.com/how-often-do-employees-need-recognition-2012-8" rel="noopener noreferrer"&gt;BI Article&lt;/a&gt;, a weekly recognition scheme is good. Probably you don't need it to be too frequent. Think for example, on sprints. If your sprints are 2 weeks long, why not dedicating some time at the end to thank your colleagues for their work?&lt;/p&gt;

&lt;p&gt;The same article says that this type of recognition helps the employee to be engaged, and trying to get that recognition again, because people like that. Try to avoid recognizing your employees just with the normal performance review cycle, because that may be too late.&lt;/p&gt;

&lt;p&gt;Again, if your colleague did something &lt;em&gt;really&lt;/em&gt; good for you or the team, don't wait to say thanks. Recognition has a "shelf life", that means, it can expire if not used at the right moment!&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%2Fthumbs.gfycat.com%2FEnchantingSafeArizonaalligatorlizard-max-1mb.gif" 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%2Fthumbs.gfycat.com%2FEnchantingSafeArizonaalligatorlizard-max-1mb.gif" alt="Apu getting expired item from the shelves" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, key points for timely recognition:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do it before it expires :)&lt;/li&gt;
&lt;li&gt;Don't wait to normal performance review cycles to recognize people&lt;/li&gt;
&lt;li&gt;Be fair&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to create a recognition culture?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Make it easy&lt;br&gt;
Your team should feel that it's easy to give recognition. If people find a lot of requirements or an extremely formal process for doing so, they won't do it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Be specific&lt;br&gt;
Saying "thanks" is good, but it's even better when you know &lt;em&gt;the reason&lt;/em&gt; for that. So saying "thanks for helping me with the last presentation" would be much more valuable than just saying thanks, because that means you're recognizing &lt;em&gt;a specific effort&lt;/em&gt;, and not just saying a canned response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect the dots :)&lt;br&gt;
Relate the recognition with your company's values. This makes easier for your team to know how they're contributing to the company's objectives and culture, and that creates a strong culture :)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make it for everyone&lt;br&gt;
Avoid having only managers giving recognition. Foster p2p recognition, since that'll create bounds between your team.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(Key points taken from &lt;a href="https://www.greatgame.com/blog/creating-a-recognition-culture-best-practices" rel="noopener noreferrer"&gt;this article&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, as conclusion, thanks for what you're doing to improve the community :) and thanks for sharing your knowledge with the rest of the people using this platform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Furw26vjcobmnds2984jb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Furw26vjcobmnds2984jb.jpg" alt="Simpsons' Monstromart: we love you" width="720" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, go there and recognize people for what they're doing :)&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>mentalhealth</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The beauty of Streams in Node</title>
      <dc:creator>Kevin Alemán</dc:creator>
      <pubDate>Fri, 02 Oct 2020 21:06:17 +0000</pubDate>
      <link>https://dev.to/kaleman15/the-beauty-of-streams-in-node-28d</link>
      <guid>https://dev.to/kaleman15/the-beauty-of-streams-in-node-28d</guid>
      <description>&lt;p&gt;Probably you have heard of them, probably you don't. But they have been around for a while. We're talking about &lt;code&gt;streams&lt;/code&gt;, an interesting and often ignored functionality of Node.JS.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are streams?
&lt;/h2&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%2Fextension.unh.edu%2Fsites%2Fdefault%2Ffiles%2Fstyles%2F2x_blog_main%2Fpublic%2F2MountainStream_MCarp.jpg%3Fitok%3DDW1-tkGH%26timestamp%3D1516995141" 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%2Fextension.unh.edu%2Fsites%2Fdefault%2Ffiles%2Fstyles%2F2x_blog_main%2Fpublic%2F2MountainStream_MCarp.jpg%3Fitok%3DDW1-tkGH%26timestamp%3D1516995141" alt="a river, or stream?" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make things easier, we will define a stream as a &lt;code&gt;sequence of data that flows freely&lt;/code&gt;. Think of streams like rivers. Rivers flow from one point to another in a constant way. The receiver doesn't know when the river will stop flowing but it's always there for receiving more water.&lt;/p&gt;

&lt;p&gt;In Node, streams are very similar. They are a constant sequence of data. They flow from one point (emitter) to another (receiver). &lt;/p&gt;

&lt;p&gt;The receiver can decide if it wants to receive data or not. Also it decides what to do with the data received. It can &lt;code&gt;ignore&lt;/code&gt; the data, &lt;code&gt;pipe&lt;/code&gt; it to another receiver, &lt;code&gt;parse&lt;/code&gt; it before receiving... &lt;/p&gt;

&lt;h2&gt;
  
  
  Why are streams useful?
&lt;/h2&gt;

&lt;p&gt;This is a really good question. If you have lived without streams your whole life, you will think that you don't need them. And, depending on your use case, this could be true.&lt;/p&gt;

&lt;p&gt;But the reality is that we need to use streams for some operations that otherwise will kill us. Let's put an example&lt;/p&gt;

&lt;h3&gt;
  
  
  Dealing with files
&lt;/h3&gt;

&lt;p&gt;Let's say we want to create a compressed copy of a file on our disk with a Node application. Usually, we will end up with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;zlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gzip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.gz&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Done!!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Imports removed for brevity&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here, we're doing 4 things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We're reading the &lt;em&gt;entire&lt;/em&gt; file and saving the data to a variable called &lt;code&gt;buffer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;We're using the &lt;code&gt;zlib.gzip&lt;/code&gt; to compress the file into a &lt;code&gt;gzip&lt;/code&gt;. We're passing the buffer to the function call. This will return a new compressed buffer&lt;/li&gt;
&lt;li&gt;We write the compressed buffer to a new location&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;console.log&lt;/code&gt; indicating that the operation finished&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Well, this works. What's the problem with this approach? You may be wondering. Well, look again at the first 2 steps. In this approach, we're reading the &lt;em&gt;whole file&lt;/em&gt; before starting to process it. Additionally, we're storing the contents of that file in memory. This is not a problem if the file size is in MB. But, if the file size is in the order of GB? Hundreds of GB? Will your computer have all that RAM available to hold the file in it? Probably no. &lt;/p&gt;

&lt;p&gt;So, this approach, even when enough for more simple tasks, represents a problem when we're looking for performance and scalability, or simply we want to support larger files.&lt;/p&gt;

&lt;p&gt;The problem can be solved by using &lt;code&gt;streams&lt;/code&gt;. Let's see how:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createGzip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWriteStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.gz&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;finish&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wooh!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're doing some things different here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We're creating a &lt;code&gt;read stream&lt;/code&gt;. This will return us &lt;code&gt;chunks&lt;/code&gt; of the file until it reaches the end.&lt;/li&gt;
&lt;li&gt;We're &lt;code&gt;piping&lt;/code&gt; the chunks to &lt;code&gt;zlib&lt;/code&gt; for compression. What's important here is that we are not waiting for the whole file to be read before starting the compression.&lt;/li&gt;
&lt;li&gt;We're creating a &lt;code&gt;write stream&lt;/code&gt; in which we're passing &lt;code&gt;chunks&lt;/code&gt; of data so Node can write them to the file.&lt;/li&gt;
&lt;li&gt;After all, we listen for the &lt;code&gt;finish&lt;/code&gt; event, which will be triggered when there's nothing more to do.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's a vast set of details &amp;amp; quirks &amp;amp; functions related to streams which will be covered in other blog posts.&lt;/p&gt;

&lt;p&gt;Hope you liked it!&lt;/p&gt;

</description>
      <category>node</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Building a new feature: talks preview</title>
      <dc:creator>Kevin Alemán</dc:creator>
      <pubDate>Fri, 11 Sep 2020 06:59:14 +0000</pubDate>
      <link>https://dev.to/kaleman15/building-a-new-feature-talks-preview-4mp0</link>
      <guid>https://dev.to/kaleman15/building-a-new-feature-talks-preview-4mp0</guid>
      <description>&lt;p&gt;I'm a backend developer, so most of the time I'm doing backend things. I feel stressed most of the time when I need to do something at the front. &lt;/p&gt;

&lt;p&gt;Then, I started to build my own personal site. And it was great! It's a blog site located temporarily at &lt;a href="https://kaleman.netlify.app" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; where you can contact me, read what I wrote, share my content with a suggested tweet, and &lt;em&gt;now&lt;/em&gt; you can see the talks I've given in my career. &lt;/p&gt;

&lt;p&gt;The talks are mostly about basic topics, one of them is in progress yet, but I wanted to showcase that I made them. And this was when the trouble begins&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vlfi3z52kggntvvn83l.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vlfi3z52kggntvvn83l.gif" alt="prepare for the problems, and make it double" width="500" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How?
&lt;/h3&gt;

&lt;p&gt;The first thing was to define what I wanted: I wanted to showcase my presentations. This was easy (in my mind), but, it wasn't easy in real life.&lt;/p&gt;

&lt;p&gt;To give you more context, I store all my presentations in Google Drive, so I can work on them wherever I go. This makes things easier for working on them, but a little bit hard if you want to show them offline.&lt;/p&gt;

&lt;p&gt;Why? Because first, you have to download the file. This is a no-brain task with a UI, just right-click and download. GG. The task gets more complex when you want &lt;em&gt;your code&lt;/em&gt; to perform that process. So I walked a lonely road, the only one that I have ever known: looking for someone who tried the same and succeeds. &lt;/p&gt;

&lt;p&gt;I found multiple &lt;a href="https://www.gatsbyjs.com/plugins" rel="noopener noreferrer"&gt;Gatsby plugins&lt;/a&gt; by just typing &lt;code&gt;drive&lt;/code&gt; or &lt;code&gt;google drive&lt;/code&gt; into the search box. I tried every single one, with the hope of finding what I wanted. Here's a list of the highlighted ones if you are trying to do something similar:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/cedricdelpoux/gatsby-source-google-docs" rel="noopener noreferrer"&gt;gatsby-source-google-docs&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem: the use case I had in mind was very different from what the library did&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/fabe/gatsby-plugin-drive" rel="noopener noreferrer"&gt;gatsby-plugin-drive&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem: it only allowed me to download DOC files (not presentations, which was what I wanted)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/jpalmieri/gatsby-source-drive" rel="noopener noreferrer"&gt;gatsby-source-drive&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem: it didn't work. Maybe I misconfigured something or so.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/maxsteenbergen/gatsby-plugin-drive" rel="noopener noreferrer"&gt;gatsby-plugin-docs-sheets&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem: it only allowed me to download Sheets. I'm pretty sure this was based on &lt;code&gt;gatsby-plugin-drive&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, since no plugin was able to fulfill my needs, I had to make a difficult decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give up on my feature&lt;/li&gt;
&lt;li&gt;Create my own plugin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I took the second, and this plugin was the result: &lt;a href="https://github.com/KevLehman/gatsby-plugin-googledrive" rel="noopener noreferrer"&gt;Gatsby-plugin-googledrive&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The entire plugin was based on a single idea: given a Google Drive's &lt;code&gt;folderID&lt;/code&gt;, download all files from the folder, and traverse the subfolders recursively to create the same structure on your selected &lt;code&gt;destination&lt;/code&gt;. And download the files for each folder.&lt;/p&gt;

&lt;p&gt;The technical details can be found at the GitHub of the project and, it's open-source, so if you want to build something around it feel free to do so.&lt;/p&gt;

&lt;h3&gt;
  
  
  How? Part 2
&lt;/h3&gt;

&lt;p&gt;After creating my plugin, I "plugged" it and the download started. I finally had the files on my local, and this was one step closer to my goal.&lt;/p&gt;

&lt;p&gt;I had the PDFs on my local, now, I wanted to do 3 things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the URL from the PDF in my filesystem (and application!)&lt;/li&gt;
&lt;li&gt;Get the first page of the PDF (the "cover page")&lt;/li&gt;
&lt;li&gt;Convert that page to an image, and show it on a nice grid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Soo, I needed 3 things: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One way to get the PDF from my filesystem&lt;/li&gt;
&lt;li&gt;One way to read the PDF&lt;/li&gt;
&lt;li&gt;One way to convert the read PDF into images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the first item, since I'm using Gatsby, I used the &lt;a href="https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-source-filesystem" rel="noopener noreferrer"&gt;gatsby-source-filesystem&lt;/a&gt; to read my folder and get the actual asset URL pointing to my file. It was really easy, just added this to my &lt;code&gt;gatsby-config.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gatsby-source-filesystem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;talks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;${__dirname}/src/talks/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! Then, to query my filesystem and get the &lt;code&gt;nodes&lt;/code&gt; with data, some &lt;code&gt;graphql&lt;/code&gt; was needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;allFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sourceInstanceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"talks"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;publicURL&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That query will do 2 things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get all the files under the &lt;code&gt;path&lt;/code&gt; of the &lt;code&gt;instance name&lt;/code&gt; called &lt;code&gt;talks&lt;/code&gt; (which is the &lt;code&gt;name&lt;/code&gt; property you pass to the plugin definition)&lt;/li&gt;
&lt;li&gt;For each &lt;code&gt;node&lt;/code&gt; aka &lt;code&gt;file&lt;/code&gt;, get its &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;publicURL&lt;/code&gt; (which is the URL that the asset will have after the building)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the second item, I had to do some research. After some looking, I finally found &lt;a href="https://github.com/mozilla/pdfjs-dist" rel="noopener noreferrer"&gt;PDF.JS&lt;/a&gt; which is like the standard for managing PDFs in JavaScript. It was written by Mozilla and has a ton of useful functions. I just used the basic API to get my feature working in a few LoC.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;PDFJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;talk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicURL&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPage&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// some code goes here&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The introduction to the library in its GitHub page was simple, maybe too small for my taste, but it worked. With this, the only thing left was to find a way to convert the PDF page's data into an actual image.&lt;/p&gt;

&lt;p&gt;I found a pretty nice approach to accomplish that. It worked like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read the PDF page&lt;/li&gt;
&lt;li&gt;Scale the page's width and height (by using the viewport of the page)&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element&lt;/li&gt;
&lt;li&gt;Set the canvas context to &lt;code&gt;2d&lt;/code&gt; (since we'll draw an image)&lt;/li&gt;
&lt;li&gt;Set the scaled page as the new data context for the &lt;code&gt;canvas&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full code was linked in this &lt;a href="https://stackoverflow.com/a/12921304" rel="noopener noreferrer"&gt;StackOverflow's answer&lt;/a&gt; which even had an example of how it worked. Nice.&lt;/p&gt;

&lt;p&gt;With all of that in place, the final result: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs0f8n6jf00jerp5mu57f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs0f8n6jf00jerp5mu57f.png" alt="My site, with the talks page populated" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hope you liked the article, feel free to also visit this post on my &lt;a href="https://kaleman.netlify.app/" rel="noopener noreferrer"&gt;own website&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Algos in real life: binary search</title>
      <dc:creator>Kevin Alemán</dc:creator>
      <pubDate>Fri, 04 Sep 2020 20:14:12 +0000</pubDate>
      <link>https://dev.to/kaleman15/algos-in-real-life-binary-search-18jf</link>
      <guid>https://dev.to/kaleman15/algos-in-real-life-binary-search-18jf</guid>
      <description>&lt;p&gt;You probably know about algorithms. You probably don't. So here's a basic definition: an algorithm is a chain of steps for moving from A to B.&lt;/p&gt;

&lt;p&gt;If you want to make a sandwich, you probably have an algorithm for that. Mine is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the ingredients&lt;/li&gt;
&lt;li&gt;Separate bread and put it on a plate&lt;/li&gt;
&lt;li&gt;Put some dressing on both halves&lt;/li&gt;
&lt;li&gt;Put lettuce, tomatoes, pickles (if any) on one half&lt;/li&gt;
&lt;li&gt;Put ham, cheese, ham again on one half&lt;/li&gt;
&lt;li&gt;Add extra things (this is, probably whatever I can find that fits my sandwich)&lt;/li&gt;
&lt;li&gt;Put the empty half over the other ingredients&lt;/li&gt;
&lt;li&gt;Eat&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whatever your process is, the point is you use an algorithm for that.&lt;/p&gt;

&lt;p&gt;Algos are really useful, in programming and real life. Here, I'll show you an example of how I used one of those algos to make my life (and work) easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  The task
&lt;/h3&gt;

&lt;p&gt;Suppose that you were assigned to solve an issue at work. Depending on the kind of issue this may be easy peasy lemon squeezy or you can end stressed depressed lemon zest. &lt;br&gt;
The issue in this example is a simple one: a function was working at some time in the past, but now it's not. Easy.&lt;/p&gt;

&lt;p&gt;You fix the function, upload your changes, and create a merge request. Everything fine at this point.&lt;/p&gt;

&lt;p&gt;Then, someone from above asks the hard question: "Hey, this worked in the past, could you tell me &lt;em&gt;when&lt;/em&gt; it stopped working?". If like mine, your project has a ton of commits, then you'll have fun, more if your commit history looks like 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.amazonaws.com%2Fuploads%2Farticles%2Flqskzvhh87ix4p3mmhru.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flqskzvhh87ix4p3mmhru.png" alt="xkcd commit history" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The solution
&lt;/h3&gt;

&lt;p&gt;You have 3 options here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cry in a corner&lt;/li&gt;
&lt;li&gt;Traverse &lt;em&gt;all&lt;/em&gt; your commits until you find one where it was working&lt;/li&gt;
&lt;li&gt;Do something good with the knowledge you have&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ok, after doing the first we can start thinking on a real solution. And here's where algorithms come handy. Let's remember one of them!&lt;/p&gt;

&lt;h4&gt;
  
  
  The binary search algo, or the principle of divide and conquer.
&lt;/h4&gt;

&lt;p&gt;You may remember that algorithm from a class, a course, some reading, or any other place. In case you haven't, I'll leave a brief explanation here: &lt;/p&gt;

&lt;p&gt;The main idea of this algorithm is to, given a sorted list and a value to find as inputs, traverse the list to find the value you're looking for.&lt;/p&gt;

&lt;p&gt;The main point of difference between this and other search algos is &lt;em&gt;how&lt;/em&gt; the list is traversed. &lt;/p&gt;

&lt;p&gt;In traditional searching, you go from the start until the end (in the worst case) to find what you're looking for. If you reach the end and you haven't found the value, then it's not there. Something like 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.amazonaws.com%2Fuploads%2Farticles%2F7iuxzyifcqe9ge9xt4d9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7iuxzyifcqe9ge9xt4d9.gif" alt="Linearsearch" width="438" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a binary search, however, you "divide" the list in halves, then compare the middle value with what you're looking for. If the value is greater, then you repeat the process in the left half, otherwise you use the right half. You repeat this until you cannot divide your input list anymore. Something like 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.amazonaws.com%2Fuploads%2Farticles%2Fdaic2okdulcwwggm1wdz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdaic2okdulcwwggm1wdz.gif" alt="Binary search" width="773" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use a binary search, you just have to follow some rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your list must be sorted&lt;/li&gt;
&lt;li&gt;Your list must be sorted&lt;/li&gt;
&lt;li&gt;Your list &lt;em&gt;must&lt;/em&gt; be sorted&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ok. You got this, the list must be sorted. Let's continue with the solution!&lt;/p&gt;

&lt;h3&gt;
  
  
  The solution (part 2)
&lt;/h3&gt;

&lt;p&gt;Since our commit history is sorted (by the date of the commit) we can perform a binary search on the commit history!&lt;/p&gt;

&lt;p&gt;I selected some old commit (like 20 days before) as my start point and my current commit as the last item. &lt;br&gt;
I, then, selected the middle one (this was made visually) and &lt;code&gt;checkout&lt;/code&gt;ed to that commit.&lt;/p&gt;

&lt;p&gt;Was functionality working? Yes? Select a more recent one! Else, use an older one.&lt;/p&gt;

&lt;p&gt;You know how the story ends. I was able to verify the whole commit history in like 20 minutes, when it may have taken 2 hours due to the number of commits in the pool.&lt;/p&gt;

&lt;p&gt;Moral: if you know how to do something fast, do it! And, algos are also useful in real life!&lt;/p&gt;

&lt;p&gt;Hope you liked this history, and let me know if it helped you! Thanks for reading 🎉&lt;/p&gt;

</description>
      <category>git</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>development</category>
    </item>
  </channel>
</rss>
