<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>JV's devlog</title>
    <link>https://blog.joaovictornsv.dev/devlog</link>
    <description>Software development notes and technical experiments</description>
    <language>en-us</language>
    <lastBuildDate>Mon, 22 Jun 2026 20:55:11 GMT</lastBuildDate>
    <item>
      <title><![CDATA[Payments on App Store, Google Play, Stripe with one RevenueCat Setup]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/revenue-cat-setup-my-docs.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/revenue-cat-setup-my-docs.html</guid>
      <pubDate>Sun, 07 Jun 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[June 7, 2026

While digging through old notes, I found a guide I wrote around two years ago about setting up revenue subscriptions and testing them locally. It covers mobile payments on the App Store and Google Play Store, plus web payments with Stripe, all wired through RevenueCat.

The RevenueCat docs are solid, so the guide doesn't repeat them. It's more of an ordered checklist: what to configure on each platform, which keys go into your settings.json, and how to test locally with sandbox acc...]]></description>
      <content:encoded><![CDATA[June 7, 2026

While digging through old notes, I found a guide I wrote around two years ago about setting up revenue subscriptions and testing them locally. It covers mobile payments on the App Store and Google Play Store, plus web payments with Stripe, all wired through RevenueCat.

The RevenueCat docs are solid, so the guide doesn't repeat them. It's more of an ordered checklist: what to configure on each platform, which keys go into your settings.json, and how to test locally with sandbox accounts, ngrok for webhooks, and the Stripe CLI.

I added it to my-docs as Revenue Cat Setup.

If you're setting up subscriptions across iOS, Android, and web, it might save you some time.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[my-docs: One Repo to Centralize All My Learnings]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/gists-to-my-docs-repo.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/gists-to-my-docs-repo.html</guid>
      <pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[June 3, 2026

For a while I kept useful notes as GitHub Gists. MongoDB troubleshooting queries, Cordova hot code push steps, AI workflow prompts. It worked, but searching across gists was awkward. When I wanted AI to read or update them, a proper repo felt simpler.

So I moved everything into my-docs. One place, normal markdown files, version history. I can grep, browse on GitHub, and point tools at the folder without juggling gist URLs.

The docs I had already written about here now live there ...]]></description>
      <content:encoded><![CDATA[June 3, 2026

For a while I kept useful notes as GitHub Gists. MongoDB troubleshooting queries, Cordova hot code push steps, AI workflow prompts. It worked, but searching across gists was awkward. When I wanted AI to read or update them, a proper repo felt simpler.

So I moved everything into my-docs. One place, normal markdown files, version history. I can grep, browse on GitHub, and point tools at the folder without juggling gist URLs.

The docs I had already written about here now live there too: the AI workflow guide (from I Asked AI How to Use It), MongoDB troubleshooting queries, and the Cordova hot code push guide. I updated those devlog posts to link to the repo instead of the old gists.

I'll probably add other docs I wrote in the past. This is mostly housekeeping, but I like having learnings in one searchable place instead of scattered gists.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[White Screens in Production: Fixing Meteor Cordova Hot Code Push]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/white-screens-production-meteor-cordova-hot-code-push.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/white-screens-production-meteor-cordova-hot-code-push.html</guid>
      <pubDate>Tue, 02 Jun 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[June 2, 2026

Recently I had to make an urgent fix on a Meteor mobile app built with Cordova. Production users were getting hit hard. The Meteor version and some Cordova plugins had been updated on the server, but the bundle on the app stores had not. Users started seeing white screens out of nowhere.

To find the issue, I downloaded the production APK from Google Play Console and opened it on an emulator. Right away I saw this message: &quot;Skipping downloading new version because the Cordova ...]]></description>
      <content:encoded><![CDATA[June 2, 2026

Recently I had to make an urgent fix on a Meteor mobile app built with Cordova. Production users were getting hit hard. The Meteor version and some Cordova plugins had been updated on the server, but the bundle on the app stores had not. Users started seeing white screens out of nowhere.

To find the issue, I downloaded the production APK from Google Play Console and opened it on an emulator. Right away I saw this message: "Skipping downloading new version because the Cordova platform version or plugin versions have changed and are potentially incompatible." A known issue. The Meteor hot code push docs describe what was going on.

The fix lives under controlling compatibility version. In short, I needed the build hashes from the bundles that were still working in the wild, so the server would serve the right bundle version to each client. The APK is basically a zip file. I opened it, searched for .json files, and found program.json, which holds the hashes I needed.

On the server I set METEOR_CORDOVA_COMPAT_VERSION_IOS and METEOR_CORDOVA_COMPAT_VERSION_ANDROID to those values (Renan helped with that part), rebuilt, and it worked. Quave One made the ops side easy: check logs, set env vars, rebuild the app, all without a long detour.

This is a bridge, not the end state. The plan is to ship a new bundle to the stores and then remove those env vars from the server once everyone can move forward on the same Cordova stack.

I saved the step-by-step notes in Cordova Hot Code Push &amp; Compat Versions on GitHub.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Test Plans Before the PR]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/ai-test-plans-before-pr.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/ai-test-plans-before-pr.html</guid>
      <pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[May 25, 2026

Small workflow note. Every PR I created with AI came with a test plan section. I liked having it there, but it always felt superficial. A short checklist, mostly happy paths, easy to skim and move on.

Now I ask for the test plan explicitly before opening the PR. Not as something the PR template fills in at the end, but as a step while I'm still in the session. The result is different. The plan gets longer and more specific. It covers paths I would skip when I'm eager to merge.

Th...]]></description>
      <content:encoded><![CDATA[May 25, 2026

Small workflow note. Every PR I created with AI came with a test plan section. I liked having it there, but it always felt superficial. A short checklist, mostly happy paths, easy to skim and move on.

Now I ask for the test plan explicitly before opening the PR. Not as something the PR template fills in at the end, but as a step while I'm still in the session. The result is different. The plan gets longer and more specific. It covers paths I would skip when I'm eager to merge.

The plans also push me past "it looks fine in the UI." They guide me to check fields in the database and verify they have the expected values. Obvious in theory, easy to skip in practice.

Same thread as We're Wasting the Real Benefits of AI. Using AI is not only about implementation speed. It can boost planning and testing too, if you ask for that on purpose.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[We're Wasting the Real Benefits of AI]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/ai-planning-time.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/ai-planning-time.html</guid>
      <pubDate>Thu, 21 May 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[May 21, 2026

I think some people see AI with the wrong perspective on time and speed. The hype is always about how fast you can ship something. It gets worse when you add the immediate culture we live in now. If the model takes more than 10 seconds to think, we get impatient. Fast outputs feel like the whole point.

Fast outputs are good. But the best part, in my experience, is the free time you get to plan the output. I don't see that promoted much.

Last week I had a simple task: implement th...]]></description>
      <content:encoded><![CDATA[May 21, 2026

I think some people see AI with the wrong perspective on time and speed. The hype is always about how fast you can ship something. It gets worse when you add the immediate culture we live in now. If the model takes more than 10 seconds to think, we get impatient. Fast outputs feel like the whole point.

Fast outputs are good. But the best part, in my experience, is the free time you get to plan the output. I don't see that promoted much.

Last week I had a simple task: implement three MongoDB schemas. The GitHub card was clear and detailed. Schema names, fields, and what each one was for. Still, I wanted to validate everything first. The feature behind this task had been discussed a lot in the background, so the requirements might have shifted.

I started a session with AI and gave it as much context as I could: a Google Docs feature briefing, a document on the main issue vs the proposed solution, a transcript from a Figma design presentation, related schemas already in the codebase, and the issue I was assigned to.

We spent about an hour discussing and validating. The result wasn't three quick schemas. It was a revised version with missing fields added, complex structures refactored, field names standardized, better collection names, and a list of questions to run by the customer. I shared the revision, got their feedback in a few minutes, and landed on a final validated version.

Now imagine I had just said: "Read this issue and implement the schemas."

My opinion is that we often waste the real benefits of AI. Not the seconds it saves typing code. The hour (or more) you get back for what actually matters: understanding the problem, catching gaps early, and aligning with people before you build the wrong thing. That's the same direction as my pre-planning approach, just applied to a smaller, concrete task.

Speed is nice. Planning time is better.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[GitHub Activity GIF]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/github-activity-gif.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/github-activity-gif.html</guid>
      <pubDate>Thu, 21 May 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[May 21, 2026

About five years ago I found Orta's GitHub profile. On that day I saw something very cool he had done: a GIF showing his last activity on GitHub. And the GIF was always updated. I was a beginner back then and had no idea how he pulled that off.

This week I remembered it for some reason, and I decided to build my own version. After a few minutes playing with Composer 2.5, I had it working. It's a simple cron job that runs six times a day. The job fetches my recent public activity u...]]></description>
      <content:encoded><![CDATA[May 21, 2026

About five years ago I found Orta's GitHub profile. On that day I saw something very cool he had done: a GIF showing his last activity on GitHub. And the GIF was always updated. I was a beginner back then and had no idea how he pulled that off.

This week I remembered it for some reason, and I decided to build my own version. After a few minutes playing with Composer 2.5, I had it working. It's a simple cron job that runs six times a day. The job fetches my recent public activity using the GitHub API, generates HTML for the last five events, converts it to a GIF, and uploads it to a Gist pinned on my GitHub profile.

It's a very simple project, but it hits different because of the nostalgia.



Source code is on GitHub.

See you next time!]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Millions of Documents, COLSCANs on Two Collections, Background Indexes]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/missing-indexes-cpu-spike.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/missing-indexes-cpu-spike.html</guid>
      <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[May 4, 2026

I did not think the MongoDB troubleshooting notes from the last post would pay off this fast. Today I was looking at another CPU peak. This time the root cause showed up almost immediately. I used the same workflow: find the heavy queries, see what they were doing, and notice they were missing indexes. So I added them.

At some point a batch of queries became the bottleneck. They filter millions of documents on one field across two different collections, which meant a lot of COLSCAN...]]></description>
      <content:encoded><![CDATA[May 4, 2026

I did not think the MongoDB troubleshooting notes from the last post would pay off this fast. Today I was looking at another CPU peak. This time the root cause showed up almost immediately. I used the same workflow: find the heavy queries, see what they were doing, and notice they were missing indexes. So I added them.

At some point a batch of queries became the bottleneck. They filter millions of documents on one field across two different collections, which meant a lot of COLSCANs. I created the indexes in the background so other work on the database could keep moving and the build itself would not spike CPU even harder. If you have not run into this yet, building indexes synchronously (not in background) can hammer CPU while it runs.

The builds took a few minutes. After they finished, the expensive queries stopped piling up because they could use the indexes. What was left was letting the queries that were already in flight finish. Once they drained, CPU dropped sharply, response times went back to normal, and watching the charts ramp down was oddly satisfying.

See you next time!]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[MongoDB Troubleshooting Queries: Saving Time on Performance Investigations]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/mongodb-troubleshooting.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/mongodb-troubleshooting.html</guid>
      <pubDate>Sat, 02 May 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[May 2, 2026

Recently, my team and I investigated a MongoDB performance issue. High CPU usage turned out to be a query problem. Nothing new, but while digging through the database, I realized we kept running the same diagnostic queries over and over.

One query that really helped was checking for long-running operations:

db.currentOp({
  secs_running: { $gte: 30 }
})

It immediately showed us which queries were taking too long. After we fixed the issues, I thought: why not save these queries so...]]></description>
      <content:encoded><![CDATA[May 2, 2026

Recently, my team and I investigated a MongoDB performance issue. High CPU usage turned out to be a query problem. Nothing new, but while digging through the database, I realized we kept running the same diagnostic queries over and over.

One query that really helped was checking for long-running operations:

db.currentOp({
  secs_running: { $gte: 30 }
})

It immediately showed us which queries were taking too long. After we fixed the issues, I thought: why not save these queries somewhere? Next time this happens (and it will), we won't waste time remembering what to run.

So I created a list of useful MongoDB troubleshooting queries. If you ever find yourself debugging MongoDB performance, it might save you some time: MongoDB Troubleshooting Queries.

See you next time!]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Bulk Operations Can Break Meteor Reactivity]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/bulk-operations-meteor.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/bulk-operations-meteor.html</guid>
      <pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[April 30, 2026

Today I hit a reactivity issue where a button stayed disabled on the client side when it shouldn't have been. Tracking it down led me to discover how bulk database operations can break Meteor's publication system.

The root cause came down to how Meteor handles optimistic updates versus server-side inserts. When you use Meteor's method stubs, the client gets an immediate visual feedback (optimistic insert), and documents appear in minimongo. But if the server then performs bulk i...]]></description>
      <content:encoded><![CDATA[April 30, 2026

Today I hit a reactivity issue where a button stayed disabled on the client side when it shouldn't have been. Tracking it down led me to discover how bulk database operations can break Meteor's publication system.

The root cause came down to how Meteor handles optimistic updates versus server-side inserts. When you use Meteor's method stubs, the client gets an immediate visual feedback (optimistic insert), and documents appear in minimongo. But if the server then performs bulk inserts that bypass the normal subscribe/publish system and hit the database directly through the raw driver, those operations can slip past Meteor's reactivity detection. The oplog, which Meteor relies on to broadcast changes to subscribers, either doesn't catch these raw operations or catches them with a delay.

So what happens is this: the client displays the optimistic insert (the button stays disabled waiting for related data), but the server's bulk inserts never reach the publication, so the subscriber never receives the data. The UI gets stuck. Refreshing the page fixes it because then the subscription runs fresh and everything is already in the database, so it gets published normally.

The lesson here is simple: if you're using Meteor's reactive system, keep your inserts going through it. Raw database operations and bulk inserts are powerful, but they're invisible to the reactivity layer. Mix them carefully.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[I Asked AI How to Use It]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/asking-ai.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/asking-ai.html</guid>
      <pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[April 28, 2026

I spent today asking an AI how to best work with it. Instead of collecting advice from blog posts and social media (where everyone shares their strategies), I figured I'd go straight to the source and ask the model itself.

I shared my current workflow, the pre-planning approach I described in my last post, and asked: what am I missing? How can I improve this?

The response wasn't about magic prompts or new techniques. It was practical, direct feedback on how to think about the i...]]></description>
      <content:encoded><![CDATA[April 28, 2026

I spent today asking an AI how to best work with it. Instead of collecting advice from blog posts and social media (where everyone shares their strategies), I figured I'd go straight to the source and ask the model itself.

I shared my current workflow, the pre-planning approach I described in my last post, and asked: what am I missing? How can I improve this?

The response wasn't about magic prompts or new techniques. It was practical, direct feedback on how to think about the interaction. Here are some of the interesting parts:

Don't describe code structure — point me to it. I can read code faster than I can parse your description of it.

Tell me what NOT to change.

Verify, don't trust. I'm good at generating plausible code. Plausible ≠ correct. Always run it. My confidence in my output has zero correlation with its correctness.

You can read the full write-up on GitHub.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Top-Level Clarity First: Why I'm Pre-Planning Before Planning]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/pre-planning.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/pre-planning.html</guid>
      <pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[April 28, 2026

I've been testing a small but meaningful change to my workflow when handling big-medium code changes. My usual process is straightforward: plan, review the plan, then build. All with the same model. (Quick note: Akita wrote an excellent post about mixing multiple models for different stages, which I recommend checking out.)

The change I'm testing is a pre-planning phase that shifts how I start the work. Instead of dumping context and immediately planning, I've added a round of c...]]></description>
      <content:encoded><![CDATA[April 28, 2026

I've been testing a small but meaningful change to my workflow when handling big-medium code changes. My usual process is straightforward: plan, review the plan, then build. All with the same model. (Quick note: Akita wrote an excellent post about mixing multiple models for different stages, which I recommend checking out.)

The change I'm testing is a pre-planning phase that shifts how I start the work. Instead of dumping context and immediately planning, I've added a round of clarification first. I tell the AI: read what I'm giving you, don't check the code yet, organize the ideas, goals, and demands, then generate a summary document. This document isn't technical. It's a concise, clear statement of what's expected.

What's happening here is I'm feeding maximum context upfront: screenshots, Slack threads, my considerations, proposed solutions, everything. The AI reads this context first, not the codebase, and synthesizes it into a shared understanding. Then I review this document before we dive deeper. If it aligns with the changes at the top level, we move to planning.

Here's a simplified example of the kind of prompt I use (not my actual one, just a reference):

You're receiving a request for a feature change. Before we start planning:

1. Read everything I'm providing: screenshots, Slack threads, my notes
2. Don't check the code yet
3. Organize the ideas, goals, demands, and constraints
4. Generate a concise summary document of what should be done
5. This summary should be clear but not technical

After this pre-planning, I'll review the document. If everything aligns, we start the actual planning.

Once we move to planning, I guide the AI to not assume anything. If it finds anything ambiguous, confused, or that might block the plan in the code itself, it should always ask before moving forward. But ideally, the pre-planning phase should have already resolved ambiguity and confusion at the top level, so we're working from solid ground.

This macro view changed how I see the work. In one case, what I initially thought was one implementation turned into three different plans instead. That shift in perspective comes from having a clear, shared understanding before diving into technical decisions.

I'm keeping this strategy in my workflow. Let's see how it plays out over time.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[I Needed to Create My Own Battery Monitor]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/battery-monitor.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/battery-monitor.html</guid>
      <pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[April 27, 2026

My Linux Mint setup stopped warning me about low battery. A few weeks ago, my PC just shut down in front of me because the battery drained completely. I searched for a way to re-enable the native warning, but couldn't find a solution.

If you're on Linux Mint or facing the same issue with your distro, here's what I did: I created a simple cron job that runs every minute, checks the battery level, and sends a notification with sound when it gets low. No overcomplicated setup, just...]]></description>
      <content:encoded><![CDATA[April 27, 2026

My Linux Mint setup stopped warning me about low battery. A few weeks ago, my PC just shut down in front of me because the battery drained completely. I searched for a way to re-enable the native warning, but couldn't find a solution.

If you're on Linux Mint or facing the same issue with your distro, here's what I did: I created a simple cron job that runs every minute, checks the battery level, and sends a notification with sound when it gets low. No overcomplicated setup, just something that works.

The script is available on GitHub. If your OS is missing battery warnings too, feel free to use it or adapt it to your needs.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Pilot]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/pilot.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/pilot.html</guid>
      <pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[April 27, 2026

This is the first post of my devlog. I've been thinking about starting a dev diary for a while, something like a git history of my day-to-day life as a developer. Not the polished reflection essays I write on my main blog, but the raw, in-the-moment stuff: what I'm learning, what tools I'm trying, what surprised me, what broke.

The main blog at blog.joaovictornsv.dev is where I sit down and think through bigger ideas about life and work. This devlog is different. It's AI-assiste...]]></description>
      <content:encoded><![CDATA[April 27, 2026

This is the first post of my devlog. I've been thinking about starting a dev diary for a while, something like a git history of my day-to-day life as a developer. Not the polished reflection essays I write on my main blog, but the raw, in-the-moment stuff: what I'm learning, what tools I'm trying, what surprised me, what broke.

The main blog at blog.joaovictornsv.dev is where I sit down and think through bigger ideas about life and work. This devlog is different. It's AI-assisted, like a rubber duck that remembers things. It's where I capture the velocity of my learning, not the philosophy behind it.

Fun fact: both sites live in the same repo. I just switch Dockerfiles depending on what I'm building. If you're curious how that works, check the repo.

Let's see where this goes.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[How to Create a Cold Wallet the Simple Way]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/how-to-create-a-cold-wallet-the-simple-way.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/how-to-create-a-cold-wallet-the-simple-way.html</guid>
      <pubDate>Sat, 21 Feb 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[February 21, 2026

This post was migrated from my main blog. blog.joaovictornsv.dev

Few weeks ago I created a cold wallet to protect my satoshis. So I decided to document the process. It's important to note that this guide is for people who understand the basics of Bitcoin. Therefore, I won't be overly detailed. I'm assuming you know the concepts and best practices mentioned below.

Why a cold wallet? Why not a hot wallet?

First, it's for study purposes. I wanted to learn how to create one and...]]></description>
      <content:encoded><![CDATA[February 21, 2026

This post was migrated from my main blog. blog.joaovictornsv.dev

Few weeks ago I created a cold wallet to protect my satoshis. So I decided to document the process. It's important to note that this guide is for people who understand the basics of Bitcoin. Therefore, I won't be overly detailed. I'm assuming you know the concepts and best practices mentioned below.

Why a cold wallet? Why not a hot wallet?

First, it's for study purposes. I wanted to learn how to create one and understand the concepts involved. Second, security. If the wallet balance starts to increase, at least it's following best self-custody practices.

Good, let's start. First, you need to set up a secure offline environment. Your seed should be generated in an environment that will never connect to the internet again.

Create the offline setup

For this, you will need a bootable USB drive with Tails. Tails is an amnesic operating system (OS) focused on security. It boots from a USB drive and all data is cleaned on shutdown.

Install Tails on a USB drive with at least 16GB of storage. After that, configure your BIOS to boot from your USB drive instead of the internal SSD or HD.

The wallet's seed will be generated on this first boot. Whenever you boot Tails, the startup will always be like a fresh setup, since the storage is cleaned on shutdown. So just start the system without any special configuration like "Persistent Storage". With Tails started, you will need to turn on the network (wired or Wi-Fi). This is the only moment you will do it. Connect to the network, access https://github.com/iancoleman/bip39/releases, and download the "bip39-standalone.html" file. Then turn off the network.

Generate seed

Open the HTML file, and you will see the "Mnemonic Code Converter" page. This is where you'll generate your wallet's private seed. Follow these steps:


  Choose whether it will have 12 or 24 words.
  Mark the "Show entropy details" checkbox. It will display a text area to input the entropy for your seed. Entropy is a way to ensure your seed is truly random. It accepts binary, base 6, 6-sided dice, base 10, hexadecimal, or cards. Choose your preference and start generating the entropy. For example, if you choose binary, you can use a coin to input entropy: heads is 1, tails is 0. Make sure to input 256 bits of entropy.
  Generate your passphrase. This part is optional. If you don't want this extra layer of security, you can skip this step.
  Now save your seed. It's the 12 words (or 24, depending on your choice) from the "BIP39 Mnemonic" field. Save your seed offline. Don't screenshot, don't take a photo, don't copy and paste. Write it on paper, but consider using a more advanced and protected method later, like writing it in metal.
  Save your passphrase if you added one.
  Also, write down the final characters of the first receiving address. This will be important during the wallet restore step. To see your receiving addresses, go to the "Derivation Path" section and select "BIP84". Then scroll to "Derived Addresses" and check the "Address" column of the addresses table. Take the first one and write down (or memorize) the final part. You can use the last 4 characters (e.g., "kjh5").


With your seed, passphrase, and address piece saved safely, shut down the system.

Access your wallet

Boot Tails again. Don't turn on the network. You will never do it again.

Open Electrum Wallet, an app already installed in Tails. You can open it by clicking the menu shortcut, but I recommend opening it via terminal as you may need to run a command first.

Open a terminal and run this command: "export QT_QPA_PLATFORM=xcb". It will force Electrum to use the "xcb" plugin to run the app. It's a workaround for compatibility issues on Linux systems. After that, run "electrum". It will open the wallet app. Now follow these steps:


  Name your wallet.
  Choose "Standalone wallet" as the wallet type.
  Choose the option "I already have a seed".
  Input your seed.
  Click "Options" and mark "BIP39" as the seed type. If you created your seed with a passphrase, mark the "Extend this seed with custom words" checkbox.
  Leave the address type marked as "native segwit".
  You can skip the password step.
  With the wallet open, click the "View" menu, then click "Show addresses".


If you entered all the information correctly, the first address in the receiving addresses list will match the characters you noted earlier. Now reboot Tails and follow the steps again to double-check that you saved all the data correctly.

Send BTC to your wallet

To do this, you need to copy one of the receiving addresses from your wallet. Here you have two options: generate a QR code (a feature of Electrum) or set up a Watch-Only wallet. I recommend setting up a Watch-Only wallet with BlueWallet. BlueWallet always provides an unused address for each transaction, which is a good practice.

To set up your Watch-Only wallet, install BlueWallet on your smartphone and import your wallet by scanning the zpub key with the generated QR code. This key is available in the "Info" option under the "Wallet" menu.

Once your Watch-Only wallet is created, click the "Receive" button and copy the address. Use it to transfer your BTCs. For testing and security purposes, transfer a small amount first. You can check the transaction status on BlueWallet. Once the transaction is confirmed, let's move to the final and most important step.

The Final Test! Very Important! Don't Skip

This is the full wallet restoration step.


  Delete your Watch-Only wallet from BlueWallet.
  Reboot Tails.
  Follow the steps I mentioned above to access your wallet.
  Check the addresses.
  Use the zpub key QR code to import the Watch-Only wallet on BlueWallet again.


If everything is correct, you'll be able to see your balance on the BlueWallet app.

Final Advice

For legal and tax purposes, don't mix BTC from different sources in the same wallet, such as from different exchanges, P2P transactions, etc.

Conclusion

Hope this guide has been useful to you! If you have any questions, feel free to contact me.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[What's the best prompt approach?]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/whats-the-best-prompt-approach.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/whats-the-best-prompt-approach.html</guid>
      <pubDate>Mon, 17 Mar 2025 00:00:00 GMT</pubDate>
      <description><![CDATA[March 17, 2025

This post was migrated from my main blog. blog.joaovictornsv.dev

Last Friday, I tried to create a simple ebook generator using Cursor and Claude Sonnet 3.7. The idea is simple: provide a topic as input, and it generates an ebook.

My initial approach was to create the entire script in one shot. However, nothing worked. I experimented with different prompts, packages, and languages, but I always encountered issues with generating or saving files.

So, I gave up and went to sleep....]]></description>
      <content:encoded><![CDATA[March 17, 2025

This post was migrated from my main blog. blog.joaovictornsv.dev

Last Friday, I tried to create a simple ebook generator using Cursor and Claude Sonnet 3.7. The idea is simple: provide a topic as input, and it generates an ebook.

My initial approach was to create the entire script in one shot. However, nothing worked. I experimented with different prompts, packages, and languages, but I always encountered issues with generating or saving files.

So, I gave up and went to sleep. The next day, I tried again, breaking down the script into individual steps: generating an ebook summary, creating chapters with AI, converting MD to PDF, merging PDF files, etc. I completed it in 1 hour, including tests and fine-tuning.

I always get better results with the second approach.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Good code vs Bad code]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/working-with-good-vs-bad-code.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/working-with-good-vs-bad-code.html</guid>
      <pubDate>Sun, 23 Feb 2025 00:00:00 GMT</pubDate>
      <description><![CDATA[February 23, 2025

This post was migrated from my main blog. blog.joaovictornsv.dev

Working 8 hours with good code &amp;gt;&amp;gt;&amp;gt; Working 1 hour with bad code

I'm not talking about modern or legacy code, or architectural approaches. It's about quality.

The main point is how much I can code without getting overwhelmed. Good code should be easy to read, understand, extend, and fix. It should minimize friction in these areas....]]></description>
      <content:encoded><![CDATA[February 23, 2025

This post was migrated from my main blog. blog.joaovictornsv.dev

Working 8 hours with good code &gt;&gt;&gt; Working 1 hour with bad code

I'm not talking about modern or legacy code, or architectural approaches. It's about quality.

The main point is how much I can code without getting overwhelmed. Good code should be easy to read, understand, extend, and fix. It should minimize friction in these areas.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Event Loop Basics: This Time You Understand]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/event-loop-basics-this-time-you-understand.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/event-loop-basics-this-time-you-understand.html</guid>
      <pubDate>Thu, 11 Apr 2024 00:00:00 GMT</pubDate>
      <description><![CDATA[April 11, 2024

This post was migrated from my main blog. blog.joaovictornsv.dev

The idea of this text is to provide a simple explanation of the Event Loop. At the end of this text, it will be easy to teach your kid or even your grandmother about Event Loop.

Introduction

I believe that the process of understanding a new subject should begin with abstractions and simplifications. And, from there, more explanations, new concepts, and specific details should be added to your knowledge. My sugges...]]></description>
      <content:encoded><![CDATA[April 11, 2024

This post was migrated from my main blog. blog.joaovictornsv.dev

The idea of this text is to provide a simple explanation of the Event Loop. At the end of this text, it will be easy to teach your kid or even your grandmother about Event Loop.

Introduction

I believe that the process of understanding a new subject should begin with abstractions and simplifications. And, from there, more explanations, new concepts, and specific details should be added to your knowledge. My suggestion for you: don't try to absolve the entire context at once, put the puzzle together piece by piece.

So, with that in mind, my goal with this post is to give you a foundation to build your Event Loop knowledge. As I said, the foundation will only be the basic, so, for now, I will omit some advanced details and concepts in the explanation.

Let's start.

Initial concepts

Callstack

Represents the JavaScript execution stack, the mechanism by which the JavaScript engine keeps track of function calls in a program.

Consider the code:

function f1() {
	console.log('Hi by f1!');
}

function f2() {
	f1();
}

f2();

When the program runs, the following steps will occur:


  f2 function call will be added to the stack. Stack size: 1
  f1 function call will be added to the stack. Stack size: 2
  console.log function call will be added to the stack. Stack size: 3
  console.log will execute and be removed from the stack. Stack size: 2
  f1 will finish and be removed from the stack. Stack size: 1
  f2 will finish and be removed from the stack. Stack size: 0


Microtask

A function executed after the function that created it exits, and only if the JavaScript execution stack is empty. Some examples of microtasks are the Promises' callback functions: .then, .catch, and .finally.

Macrotask

A function that executes after the JavaScript execution stack and microtask have both been cleared. Macrotask represents some discrete and independent work. Some examples of macro tasks are the callback functions of the asynchronous timer: setTimeout, setInterval, and setInterval.

The loop

The Event Loop is responsible for orchestrating the execution of synchronous and asynchronous code in Node.js. It runs continuously while the Javascript code is executing.

In many posts about Event Loop, you will find a great and detailed explanation of how it works, the phases of execution, and which queues are managed. Well, the focus here is to make you understand the basics, so the explanation will be very simplified. But remember, this is your foundation to learn more in the future.

I will borrow and describe the good explanation presented in the video Aula Essencial de Javascript que Você Perdeu from the Dev Junior Alves channel on YouTube.

The loop consists of three phases:


  Execute synchronous code and queue microtasks and macrotasks
  Execute callbacks on microtasks queue
  Execute callbacks on macrotasks queue


Let's understand that process using this code example:

console.log("1");

setTimeout(() =&gt; {
	console.log("2");
}, 0);

Promise.resolve().then(() =&gt; {
	console.log("3");
});

console.log("4");

Before continuing, try to figure out the code output based only on your knowledge and on what was said above.

Let's move on. The output will be:

1
4
3
2

Okay, why? Let's understand.

Now let's read the code again, but with the initial concepts and the three phases of Event Loop in mind.

// A synchronous code, execute it.
// Output: 1
console.log("1");

// Oh, a setTimeout function, this is a macrotask.
// Let's send the callback to the Macrotasks Queue.
// Output: 1
setTimeout(() =&gt; {
	console.log("2");
}, 0);

// Hmm, a Promise, a microtask.
// Let's send the .then callback to the Microtask Queue.
// Output: 1
Promise.resolve().then(() =&gt; {
	console.log("3");
});

// Other synchronous code, run it.
// Output: 1 4
console.log("4");

// -----------------------------

// Okay, now let's run the Microtask Queue:
// Output: 1 4 3

// Finally let's run the Macrotasks Queue:
// Output: 1 4 3 2

// Callstack, Microtask Queue, and Macrotasks Queue are empty.
// Then, THE END.

Next steps

Well, now you understand in a simplified way how Event Loop works. It is no longer a seven-headed monster, perhaps a three-headed one. From now on it's up to you to build your knowledge above this base.

"How can I do this?". Watch, read, and practice until you get tired of the Event Loop and until it becomes a fixed concept in your mind. I will leave links to some videos and posts below.

And finally, explain it to other people: record a video, write a blog post, call a friend, etc.

I hope this post was helpful to you! See you next time :)

Useful links

Videos


  🇧🇷 Aula Essencial de Javascript que Você Perdeu
  🇧🇷 Async, Promises, Callbacks, Event Loop - JS
  🇺🇸 What the heck is the event loop anyway?
  🇺🇸 Inside the Loop


Posts


  A complete guide to the Node.js event loop
  A Complete Visual Guide to Understanding the Node.js Event Loop]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Techocalypse Now: Extreme Hypotheticals for Developers]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/techocalypse-now-extreme-hypotheticals-for-developers.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/techocalypse-now-extreme-hypotheticals-for-developers.html</guid>
      <pubDate>Fri, 15 Mar 2024 00:00:00 GMT</pubDate>
      <description><![CDATA[March 15, 2024

This post was migrated from my main blog. blog.joaovictornsv.dev

I recently read a blog post called Extreme brainstorming questions to trigger new, better ideas written by Jason Cohen. In summary, the text shows several extreme scenarios that can affect a business model and invites the reader to think about what to do in each of them. I have discussed this with friends and it was a good conversation and generated a lot of insights in my head.

So, I decided to write something si...]]></description>
      <content:encoded><![CDATA[March 15, 2024

This post was migrated from my main blog. blog.joaovictornsv.dev

I recently read a blog post called Extreme brainstorming questions to trigger new, better ideas written by Jason Cohen. In summary, the text shows several extreme scenarios that can affect a business model and invites the reader to think about what to do in each of them. I have discussed this with friends and it was a good conversation and generated a lot of insights in my head.

So, I decided to write something similar but focused on the developer industry. I hope the following text can provoke some good ideas for you too.

Introduction

You're probably used to your developer routine: coding, solving bugs, reading docs, committing and pushing, looking for good job opportunities, etc. But, today I'm going to take you on a different journey that will make you use your brain to think outside the code box. Please take the time to read this calmly. Read it once, breathe, take a note, discuss it with a friend, and read it again.

The following sections will focus on describing extreme hypothetical scenarios in the developer world and will provoke you to think about what would be your position in each of them, and of course, whether you are prepared for them.

Let's start.

Decentralized codebase

Imagine a scenario where all version control platforms, like GitHub, GitLab, and Bitbucket have disappeared. Forget about all the cloud integrations and automated CI/CD pipelines, just focus on the impact of this scenario on your career and the open-source community.

How would you show your contributions, projects, and acknowledgments to the tech community? What approaches will you take to ensure your work is recognized and appreciated?

The question is not only "Where to save my code?", but "How can my projects and contributions be found?" or "How can I contribute to other interesting projects together with other people?"

Offline Problem-Solving

Consider the scenario where online forums like Stack Overflow are inaccessible. How would you handle technical challenges, troubleshoot issues, and resolve bugs without the resources and knowledge of online communities?

Another way to think about this lack of help situation is: Does my product (app, website, package, etc) provide great documentation? Are there sections focused on troubleshooting and answering common questions from my customers?

Alternative Visibility Strategies

Suppose LinkedIn and other job networking platforms ceased to exist. In what way do you will make yourself visible to employers and tech companies, ensuring they recognize your skills, experiences, and contributions? How will you find new job opportunities or potential clients for your projects?

Other questions to keep in mind are: Does widespread competition among developers continue? Who will take the advantage in this extreme scenario? Are you part of the group that will easily adapt to this situation?

That would be a time when you will need to improve your marketing skills and learn how to promote yourself more efficiently, and obviously, use the power of social media algorithms to your advantage. By the way, have you ever exposed yourself on another platform? In which places can you and your content be found?

Innovative Self-Presentation

Wonder a world where traditional resumes and Curriculum Vitae (CV) were banned. Combined with this, it is now AI's responsibility to describe your skills based on your activity in the tech community, such as commits, blog posts, videos, PRs, opened issues, etc.

How would you present your professional background, experiences, skills, and accomplishments to potential employers and clients, ensuring they understand your value and capabilities?

Where will you show your difference to the world? How can you prove your knowledge and skills? And finally, how convince potential clients or recruiters that you are the best choice they can make?

Adapting to Language Restrictions

Think about a situation where the programming language you're most proficient in (or more familiar with) was banned from the industry. How would you quickly adapt your skill set to alternative languages or technologies and remain a competitive and valuable developer?

Are you ready to let go of your pet language? Have you ever understood that programming languages, packages, and frameworks are just tools? Do you know any mechanic who has a favorite hammer and hates screwdrivers?

It's time to see how good and prepared your toolbox is.

Overflow of Job Opportunities

In a market flooded with developer job opportunities, each offering competitive salaries and attractive perks, how would you strategically choose the right career path and employer to maximize your professional growth and fulfillment?

In this situation, you need to take care of the Analysis Paralysis. How will you filter the best companies? Do you already know how to do this today?

Stay calm, it's essential to rationalize all the trade-offs presented at each opportunity. Are benefits really benefits? Who will work with? What are people saying about this company?

Maximize fun

Ignore money and responsibilities. You have infinite money and no more meetings to join. What will be the funniest and most enjoyable thing you will develop? What do you want to do? It can be anything, testing a new framework, creating our programming language, or a new game. Just follow your dreams.

Do you prefer to create something to have fun alone, with friends, or with the world? Think outside the IDE, there are many activities to do outside your room, open a presential code club, promote a programming event, start writing a book, or just simple blog posts, etc.

Philanthropist

The scenario is the same as described above, but the question is different. What would you do to make the world a better place? What people will you help? What problems you will solve? Remember, now you have all the money and all the time.

Well, don't feel pressured. You don't need to literally save the world, just take time to think about the difficulties you or other people face, whether small or large. Can I automate something? Are there some activities that can I delegate to the code?

And finally, how my skills and experience as a developer can help others in general?

AI Domination

This is not a dystopic scenario, is real life. AI is evolving more and more and we have no idea where we are going. The future is now.

Okay, stop with this apocalyptic tone. Do you know what AI is and how it works? How much do you understand the limits of AI?

What would make a company choose you over an AI? Are you more valuable than a GPT tool? Can a machine completely replace you?

The AI age has begun. Will you survive?]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Exploring Terraform: Your First Guide to Infrastructure as Code]]></title>
      <link>https://blog.joaovictornsv.dev/devlog/exploring-terraform-your-first-guide-to-infrastructure-as-code.html</link>
      <guid isPermaLink="true">https://blog.joaovictornsv.dev/devlog/exploring-terraform-your-first-guide-to-infrastructure-as-code.html</guid>
      <pubDate>Fri, 10 Feb 2023 00:00:00 GMT</pubDate>
      <description><![CDATA[February 10, 2023

This post was migrated from my main blog. blog.joaovictornsv.dev

Today, I want to show you the power of Terraform in action with a simple integration that you can follow while as you read the steps below.
But first, we need to understand what Terraform is.

What is Terraform?

In short, Terraform is a tool for managing resources across different providers (AWS, GCP, Azure, GitHub, Kubernetes, etc.), all in an automated way through lines of code! If you want to dive deeper int...]]></description>
      <content:encoded><![CDATA[February 10, 2023

This post was migrated from my main blog. blog.joaovictornsv.dev

Today, I want to show you the power of Terraform in action with a simple integration that you can follow while as you read the steps below.
But first, we need to understand what Terraform is.

What is Terraform?

In short, Terraform is a tool for managing resources across different providers (AWS, GCP, Azure, GitHub, Kubernetes, etc.), all in an automated way through lines of code! If you want to dive deeper into these capabilities, I highly recommend checking out the Terraform website.

Prerequisites

To proceed with the next steps, you need to have the Terraform installed in you machine. Installing it is simple, check the docs for more details. Additionally, Docker must be installed for this integration.

Hands on

Let's start by pulling a Docker image and running a container using only Terraform! Keep in mind that this post won't cover all the details. As mentioned in the title, this is just a simple introduction to the tool.

1. Setting the Providers

I'm using the example described here in the Terraform documentation.

First, create a main.tf file and paste the following code:

terraform {
    required_providers {
        docker = {
            source  = "kreuzwerker/docker"
            version = "3.0.1"
        }
    }
}

provider "docker" {
    host = "unix:///var/run/docker.sock"
}

This is how we tell Terraform that Docker will be our resource provider.

2. Defining Our Resources: Image and Container

Now, in the same file, let's pull a Docker image. For this example, I will use the nginx image:

resource "docker_image" "nginx" {
    name = "nginx:1.23.3-alpine"
}

The resource block has two key elements:


  Resource Type: Specifies the type of resource to create. In this case, it's a Docker image.
  Resource Identifier: A unique name to differentiate resources of the same type. This identifier is also crucial for referencing the resource later.


Let's use this identifier to create our container:

resource "docker_container" "nginx" {
    image = docker_image.nginx.image_id
    name  = "nginx_alpine"

    ports {
        internal = 80
        external = 8080
    }
}

Here, we are creating a container based on the previously defined image, while setting the container name and its ports.

If you'd like to explore all available settings for these resources, consider reviewing their documentation:


  Documentation for docker_image: registry.terraform.io/providers/kreuzwerker/docker/latest/docs/resources/image
  Documentation for docker_container: registry.terraform.io/providers/kreuzwerker/docker/latest/docs/resources/container


3. Creating the Resources

To ensure that our resource configurations are valid, run the following command:

terraform validate

Next, execute:

terraform plan

This command generates a report showing the changes Terraform will make. At the end of the output, you'll see a summary of what will be created, modified, or destroyed:

Plan: 2 to add, 0 to change, 0 to destroy.

To apply the changes, use:

terraform apply

Terraform will ask for confirmation. Just type yes to proceed (you can skip this step by using the -auto-approve flag). Once the changes are applied, wait for Terraform to complete the process. If successful, you should see this message in your terminal:

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Finally, verify that the resources were created by running these Docker commands:

docker image ls
docker container ls

Bonus

In this post, I have only covered the basics of integrating Terraform with Docker to keep it concise and interesting. If you're interested in exploring more advanced topics, such as separating resources into different files, defining variables, or understanding the terraform.tfstate file, check out the video I recorded (in Portuguese). It goes beyond the steps described here and dives into these additional details.

Conclusion

This is the power of Terraform! I hope you're feeling motivated to dive even deeper into it.]]></content:encoded>
    </item>
  </channel>
</rss>