<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Projects on Vitor Pontual | The VeePee Hub</title>
    <link>https://vitorpontual.com/categories/projects/</link>
    <description>Recent content in Projects on Vitor Pontual | The VeePee Hub</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Tue, 23 Jun 2026 07:00:00 +0000</lastBuildDate>
    <atom:link href="https://vitorpontual.com/categories/projects/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>VeePeenini, Part 11: What Moving a Live App Taught Me</title>
      <link>https://vitorpontual.com/posts/veepeenini-11-what-moving-a-live-app-taught-me/</link>
      <pubDate>Tue, 23 Jun 2026 07:00:00 +0000</pubDate>
      <guid>https://vitorpontual.com/posts/veepeenini-11-what-moving-a-live-app-taught-me/</guid>
      <description>&lt;p&gt;The move worked. The game runs from the second location now, every point and sticker accounted for. A few things stuck with me, including one I got plain lucky on and one that an old decision quietly handled for me.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-downtime-was-the-feature-not-the-failure&#34;&gt;The downtime was the feature, not the failure&lt;/h2&gt;&#xA;&lt;p&gt;The reflex with any cutover is to chase zero downtime. I went the other way on purpose. With two separate databases and no shared storage, that brief &amp;ldquo;front door down&amp;rdquo; window is the only moment I can prove both sides hold exactly the same data. Trying to eliminate it would have traded a visible ninety-second blip for an invisible risk of a lost trade. For a game whose entire value is that the standings are trustworthy, that is a terrible trade. Ugly and certain beat smooth and sorry.&lt;/p&gt;</description>
    </item>
    <item>
      <title>VeePeenini, Part 10: Ninety Seconds of Downtime</title>
      <link>https://vitorpontual.com/posts/veepeenini-10-ninety-seconds-of-downtime/</link>
      <pubDate>Tue, 23 Jun 2026 06:00:00 +0000</pubDate>
      <guid>https://vitorpontual.com/posts/veepeenini-10-ninety-seconds-of-downtime/</guid>
      <description>&lt;p&gt;The plan from &lt;a href=&#34;https://vitorpontual.com/posts/veepeenini-9-the-plan-and-all-the-ways-it-was-wrong/&#34;&gt;Part 9&lt;/a&gt; was good. Running it live, while my friends were mid-tournament, is a different feeling. Here is how it actually went, step by step, because the calm version is the one worth remembering.&lt;/p&gt;&#xA;&lt;h2 id=&#34;wait-for-finished-not-for-the-whistle&#34;&gt;Wait for &amp;ldquo;finished,&amp;rdquo; not for the whistle&lt;/h2&gt;&#xA;&lt;p&gt;First I waited for a match to be genuinely done, which is not the same as the final whistle. The app grades a match a minute or two after it ends: it settles every prediction, awards the points, and drops the loot packs. If I had taken my copy of the database in that window, I&amp;rsquo;d have captured a half-graded match and lost the points that landed a moment later.&lt;/p&gt;</description>
    </item>
    <item>
      <title>VeePeenini, Part 9: The Plan, and All the Ways It Was Wrong</title>
      <link>https://vitorpontual.com/posts/veepeenini-9-the-plan-and-all-the-ways-it-was-wrong/</link>
      <pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://vitorpontual.com/posts/veepeenini-9-the-plan-and-all-the-ways-it-was-wrong/</guid>
      <description>&lt;p&gt;On paper the plan was one sentence: copy the database from the old box to the new one, point the front door at the new box, done. Everything that mattered lived in the gap between that sentence and a version I would actually trust with my friends&amp;rsquo; data. Closing that gap is most of what this entry is.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-boring-problems-that-were-actually-the-dangerous-ones&#34;&gt;The boring problems that were actually the dangerous ones&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;The tunnel wasn&amp;rsquo;t only the app&amp;rsquo;s.&lt;/strong&gt; The Cloudflare Tunnel in front of the game also carried a couple of my other personal services. &amp;ldquo;Just move the tunnel to the new box&amp;rdquo; would have quietly knocked those over too. So the call was to give the app its own dedicated tunnel, and at switch time repoint only the app&amp;rsquo;s address to it, leaving everything else untouched.&lt;/p&gt;</description>
    </item>
    <item>
      <title>VeePeenini, Part 8: Moving the Server Without Losing the Game</title>
      <link>https://vitorpontual.com/posts/veepeenini-8-moving-the-server/</link>
      <pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://vitorpontual.com/posts/veepeenini-8-moving-the-server/</guid>
      <description>&lt;p&gt;For a few weeks the app just worked. My friends were opening packs every morning, filling their albums, arguing in the lounge, making predictions before kickoff. The build was behind me and the fun was the point. Then a logistics problem landed on top of all of it: the whole thing runs on a server in my home, and a move was coming that meant shutting that server down and bringing the app back up at a second location.&lt;/p&gt;</description>
    </item>
    <item>
      <title>VeePeenini, Part 7: What&#39;s Next</title>
      <link>https://vitorpontual.com/posts/veepeenini-7-whats-next/</link>
      <pubDate>Thu, 04 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://vitorpontual.com/posts/veepeenini-7-whats-next/</guid>
      <description>&lt;p&gt;Everything in the first six parts was me building against a deadline. This part is the opposite: the build is done, and now I wait for the only test that matters. The World Cup starts on June 11. Until a real match kicks off with my friends all logged in at once, a lot of what I built is a well-reasoned guess.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s what I&amp;rsquo;m actually watching for.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://vitorpontual.com/images/projects/veepeenini/pack-opening.png&#34; alt=&#34;Opening a pack&#34;&gt;&#xA;&lt;em&gt;Ripping open a pack. The build is done; the tournament is the test.&lt;/em&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>VeePeenini, Part 6: Quality of Life</title>
      <link>https://vitorpontual.com/posts/veepeenini-6-quality-of-life/</link>
      <pubDate>Wed, 03 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://vitorpontual.com/posts/veepeenini-6-quality-of-life/</guid>
      <description>&lt;p&gt;With the game fair and the lounge live, the rest of the work was quality of life. None of it is headline material. All of it decides whether the app earns a spot on the home screen or gets swiped off it. This is the stuff I find most satisfying to get right, because it&amp;rsquo;s invisible when it works and grating when it doesn&amp;rsquo;t.&lt;/p&gt;&#xA;&lt;h2 id=&#34;push-notifications-and-an-app-icon-badge&#34;&gt;Push notifications and an app-icon badge&lt;/h2&gt;&#xA;&lt;p&gt;A trade is a social act. Someone makes you an offer and then waits. If you don&amp;rsquo;t know it&amp;rsquo;s there, the whole loop stalls. So the app got web push: you get pinged when someone offers, counters, accepts, or declines a trade. And the installed app icon carries a number badge, the same red dot you know from every other app, that works even while the app is closed.&lt;/p&gt;</description>
    </item>
    <item>
      <title>VeePeenini, Part 5: Going Live</title>
      <link>https://vitorpontual.com/posts/veepeenini-5-going-live/</link>
      <pubDate>Tue, 02 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://vitorpontual.com/posts/veepeenini-5-going-live/</guid>
      <description>&lt;p&gt;The last stretch before kickoff was about one feeling: when a match is on, the app should feel like everyone is in the same room. The album is a between-matches thing. The lounge is a during-the-match thing, and it needed real reasons to be open while a game is playing.&lt;/p&gt;&#xA;&lt;h2 id=&#34;half-drops&#34;&gt;Half-drops&lt;/h2&gt;&#xA;&lt;p&gt;The core new lever is half-drops. During each half of a live match, everyone can claim one free card, but only from inside the lounge, and only while that half is actually running. Miss the window and it&amp;rsquo;s gone. Two cards a match, max. It rewards being there, live, with the group.&lt;/p&gt;</description>
    </item>
    <item>
      <title>VeePeenini, Part 4: Correctness Before Kickoff</title>
      <link>https://vitorpontual.com/posts/veepeenini-4-correctness-before-kickoff/</link>
      <pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://vitorpontual.com/posts/veepeenini-4-correctness-before-kickoff/</guid>
      <description>&lt;p&gt;With the first match a few days out, my priorities flipped. Up to now the question had been &amp;ldquo;what should this do next.&amp;rdquo; Now it was &amp;ldquo;can I promise this won&amp;rsquo;t lose anyone&amp;rsquo;s cards or mis-grade a prediction.&amp;rdquo; Those are different jobs. The first is additive and fun. The second is paranoid and quiet, and it&amp;rsquo;s the one that decides whether a group of friends still trusts the app in week three.&lt;/p&gt;</description>
    </item>
    <item>
      <title>VeePeenini, Part 3: Launch and the Long Tail of Data</title>
      <link>https://vitorpontual.com/posts/veepeenini-3-launch-and-the-long-tail/</link>
      <pubDate>Fri, 29 May 2026 00:00:00 +0000</pubDate>
      <guid>https://vitorpontual.com/posts/veepeenini-3-launch-and-the-long-tail/</guid>
      <description>&lt;p&gt;The day after launch, one of my friends sent me a message: a group on the bracket was showing the wrong teams. Not a crash, not an error page. Just quietly, confidently wrong. This is exactly the kind of bug you only catch once real people are looking, because they know the tournament better than your test data does.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-bug-that-was-really-two-files-arguing&#34;&gt;The bug that was really two files arguing&lt;/h2&gt;&#xA;&lt;p&gt;The group letters were wrong on the bracket but right everywhere the actual fixtures were involved. When I dug in with Claude, the cause was almost funny: two different files in the codebase each held their own hardcoded copy of which team is in which group, and over a week of changes they had drifted apart. Nine of the twelve groups had at least one wrong assignment in one of the files. The database, the actual source of truth, had been correct the whole time.&lt;/p&gt;</description>
    </item>
    <item>
      <title>VeePeenini, Part 2: Making It Real</title>
      <link>https://vitorpontual.com/posts/veepeenini-2-making-it-real/</link>
      <pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate>
      <guid>https://vitorpontual.com/posts/veepeenini-2-making-it-real/</guid>
      <description>&lt;p&gt;By the end of day one VeePeenini ran. That is a very different thing from being ready to put in front of people I actually like. The next two days were about the unglamorous distance between those two states, and most of it was product work, not new features.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-auth-reckoning&#34;&gt;The auth reckoning&lt;/h2&gt;&#xA;&lt;p&gt;My first auth choice was magic links. Click a link in your email, you&amp;rsquo;re in. Clean, no passwords to manage. Except it broke the moment the app went behind a Cloudflare Tunnel. The verification token is tied to the host that issued it, and the proxied host didn&amp;rsquo;t match, so every link came back invalid. I spent a little time poking at it and then made a product call: this is a closed group of about a dozen people I know personally. I don&amp;rsquo;t need self-serve signup. I&amp;rsquo;ll provision the accounts myself and hand out passwords.&lt;/p&gt;</description>
    </item>
    <item>
      <title>VeePeenini, Part 1: The Idea and Day One</title>
      <link>https://vitorpontual.com/posts/veepeenini-1-the-idea-and-day-one/</link>
      <pubDate>Sun, 24 May 2026 00:00:00 +0000</pubDate>
      <guid>https://vitorpontual.com/posts/veepeenini-1-the-idea-and-day-one/</guid>
      <description>&lt;p&gt;The idea showed up on a plane. I was flying back from Las Vegas on May 24 with a few empty hours and nothing to do, and I started sketching out a World Cup app for my friends. The tournament kicks off June 11, so I&amp;rsquo;d handed myself a little under three weeks. That&amp;rsquo;s exactly the kind of deadline that forces you to scope hard and skip everything that doesn&amp;rsquo;t matter.&lt;/p&gt;</description>
    </item>
    <item>
      <title>The Daily Vee: Building a Personal AI Podcast</title>
      <link>https://vitorpontual.com/posts/the-daily-vee/</link>
      <pubDate>Wed, 01 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://vitorpontual.com/posts/the-daily-vee/</guid>
      <description>&lt;p&gt;I wanted a morning news briefing that covered exactly the topics I care about — tech, science, crypto, AI, and the occasional sports story — without the ads, the hot takes, or the 45-minute runtime. So I built one.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The Daily Vee&lt;/strong&gt; is a fully automated podcast that runs every day on my homelab. It pulls stories from my AI news aggregator (&lt;a href=&#34;https://vitorpontual.com/posts/february-shipping-spree/&#34;&gt;The VP Journal&lt;/a&gt;), writes a two-host script using a local LLM, voices it with neural text-to-speech, stitches the audio together, and publishes the episode to Telegram, Audiobookshelf, and right here on this site. No cloud APIs. No subscriptions. All local.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
