<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://berens.co/feed.xml" rel="self" type="application/atom+xml" /><link href="https://berens.co/" rel="alternate" type="text/html" /><updated>2026-05-09T08:28:17+00:00</updated><id>https://berens.co/feed.xml</id><title type="html">Paul Berens</title><subtitle>berens.co</subtitle><entry><title type="html">Humani Nihil a Me Alienum Puto</title><link href="https://berens.co/good-and-evil.html" rel="alternate" type="text/html" title="Humani Nihil a Me Alienum Puto" /><published>2026-05-06T00:00:00+00:00</published><updated>2026-05-06T00:00:00+00:00</updated><id>https://berens.co/good-and-evil</id><content type="html" xml:base="https://berens.co/good-and-evil.html"><![CDATA[<p><img src="/assets/og/post_taking_of_christ-caravaggio.jpg" alt="Taking of Christ, Caravaggio, 1602" />
<span class="muted small"><i>The Taking of Christ,</i> Caravaggio (c. 1602).</span></p>

<p>Remember back in the day when you played Cops &amp; Robbers? There was clarity about evil: good guys and bad guys. Pretty simple. And perhaps attractive in that it keeps the world simple.</p>

<p>Of course, life and literature<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> instruct us that it’s more complicated than that. But there are narratives that keep bringing us back to cosmic dualism: maybe best expressed in our <strong>cancel culture</strong> or <strong>true crime culture</strong>, virtue signaling through performative outrage.</p>

<p>Those condemning others in the town square (Twitter/X) need first to put some distance between themselves and the other guys: affirmation of self; discounting and forgetting my own sins. And then the move is: I would never do that. I would never say that. Because I’m a pretty good guy. Ergo that guy is evil.</p>

<p>In fact there’s a concept called <strong>moral pollution</strong> which describes acting like bad reputations are contagious, and mere proximity to something labelled immoral is dangerous/toxic. Brands cut ties with people deemed unethical not because they value ethics, but because they fear contamination (cancellation as moral quarantine).</p>

<p>But Aleksandr I. Solzhenitsyn is much more thoughtful in <em>The Gulag Archipelago:</em></p>

<blockquote>
  <p>Gradually it was disclosed to me that the line separating good and evil passes not through states, nor between classes, nor between political parties either—but right through every human heart—and through all human hearts. This line shifts. Inside us, it oscillates with the years. And even within hearts overwhelmed by evil, one small bridgehead of good is retained. And even in the best of all hearts, there remains…an unuprooted small corner of evil.</p>
</blockquote>

<p>That’s not to say we can’t and ought not name evil and seek it out and fight it courageously. But it does mean that we should do so with humility and not triumphalism.</p>

<p>The Roman playwright Terence put it this way: <em>Homo sum, humani nihil a me alienum puto</em> (“I am a human being; nothing human is alien to me.”) Maya Angelou returned to this line, and explained why it matters:</p>

<blockquote>
  <p>‘I am a human being. Nothing human can be alien to me.’ If you can internalize at least a portion of that, you will never be able to say of an act, of a criminal act, ‘Well I couldn’t do that.’ No matter how heinous the crime, if a human being did it you have to say ‘I have in me all the components that are in her, or in him…’</p>
</blockquote>

<p>St. Nicholas remembers all children, though in Hungary he leaves both treats and some twigs (a switch) because no one is either all good nor all bad.</p>

<p>Because after all, sin <em>(mysterium iniquitatis)</em> entered the world and we’re naive if we deny it or put our blinders on. Remember the line from <em>The Usual Suspects</em>: “The greatest trick the Devil ever pulled was convincing the world he didn’t exist.” There’s indeed a dangerous trap to avoid: if I think myself too “good” I might move toward believing I’m not in need of a savior—save me from what?—which might mean I’ve already “earned” heaven from being such a good guy. This is <strong>Pelagianism</strong> and it’s rampant.</p>

<p>Revelation 3:16-17:</p>
<blockquote>
  <p>So, because you are lukewarm, neither hot nor cold, I will spit you out of my mouth. For you say, ‘I am rich and affluent and have no need of anything,’ and yet do not realize that you are wretched, pitiable, poor, blind, and naked.</p>
</blockquote>

<p>If that’s the Christian lens, the behavioral economics lens is the <a href="/systems/"><strong>Licensing Effect</strong></a>, where believing you’re good can actually make you behave badly — those who consider themselves virtuous worry less about their own behavior, making them more susceptible to ethical lapses.</p>

<p>And how do you end up considering yourself virtuous? By defining virtue<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> yourself, on your own terms; as opposed to looking to scripture and law, the saints and philosophers, etc.</p>

<p>We can’t stop naming and calling out and thwarting evil lest we become evil, but that must be balanced with humility.</p>

<p>Dom Christian de Chergé was a Trappist monk who remained in Algeria knowing he would likely be killed…and was. Shortly before his martyrdom, he wrote: “I have lived long enough to know that I am an accomplice in the evil which seems to prevail so terribly in the world, even in the evil which might blindly strike me down.”</p>

<p>Check out the Caravaggio at the top of the page. The figure holding the lantern at right is believed to be Caravaggio himself, present at the betrayal and looking on. He doesn’t exempt himself from the scene…and neither can we.</p>

<p>Maybe the nuance here is that instead of letting people off the hook, there is still an accounting; but instead of demonizing and continuing to hold in contempt, this humility we speak of requires <strong>forgiveness</strong>. C. S. Lewis says that to be a Christian means to forgive the inexcusable because God has forgiven the inexcusable in you.</p>

<p>The theological ground for this is <em>Imago Dei</em>: that every human being, however contemptible by their actions and disfigured by sin, bears the image and likeness of God. Sin obscures that image, but cannot erase it. Despite everything, they remain a child of God — someone for whom Christ died.</p>

<p>This is the corrective to both the cancel culture impulse and the Pelagian trap: not the relativizing <em>I guess what they did wasn’t so bad,</em> but <em>I share in the same fallen nature, and I am commanded to love what God loves.</em></p>

<p>— ᴘ. ᴍ. ʙ.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><em>The Brothers Karamazov,</em> <em>Les Misérables,</em> <em>Hamlet,</em> <em>Fargo,</em> etc. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>I won’t go all the way down the relativism rabbit hole, but this reminds me of Bishop Robert Barron’s frequent citation of the 1992 Supreme Court case <em>Planned Parenthood v. Casey</em>, where he highlights the passage authored by Justice Kennedy: “At the heart of liberty is the right to define one’s own concept of existence, of meaning, of the universe, and of the mystery of human life.” <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Paul Berens</name></author><category term="theology" /><category term="culture" /><category term="first principles" /><summary type="html"><![CDATA[The line separating good and evil passes not through states, nor classes, nor political parties; but through every human heart.]]></summary></entry><entry><title type="html">Swarma</title><link href="https://berens.co/swarma.html" rel="alternate" type="text/html" title="Swarma" /><published>2026-03-29T00:00:00+00:00</published><updated>2026-03-29T00:00:00+00:00</updated><id>https://berens.co/swarma</id><content type="html" xml:base="https://berens.co/swarma.html"><![CDATA[<p>I’m old school fringe consumer tech: <a href="https://en.wikipedia.org/wiki/Path_(social_network)">Path</a> for social media, <a href="https://en.wikipedia.org/wiki/Songza">Songza</a> for music, Foursquare—now <a href="https://swarmapp.com/">Swarm</a>—for lifelogging.</p>

<p>I’ve also deepened my commitment to Google hardware over the years—first Google Home, then <a href="/phones/">Pixel phone</a>, now Pixel Watch.</p>

<p>But there’s no Swarm app on Wear OS (!), which leaves the 63 of us in this cross-sectional segment with a choice: find and beg a Swarm product manager to prioritize it in the backlog, or vibe-code it.</p>

<p>Option B is a lot more realistic (and fun), and thus <strong>Swarma</strong><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> was born.</p>

<p>This is my third or fourth <a href="/vibe-coding.html">vibe-coding</a> project with Claude as co-pilot: I’ve done <a href="/now/">APIs</a>, a <a href="/song-bot.html">Telegram bot</a>, <a href="/dark-mode.html">dark mode</a>, etc. The pattern is familiar by now: describe what you want, iterate, ship. But this time the <em>platform</em> was completely new to me: Android Studio, Kotlin, Jetpack Compose, Wear OS.</p>

<p>The core app logic—hitting the Foursquare Places API, grabbing nearby venues, displaying a scrollable list, etc.—came together relatively easily. Claude handled the Kotlin syntax, the Compose UI components, the API calls. Same as it would in JavaScript or Python.</p>

<p>What <em>wasn’t</em> interchangeable was everything around the code:</p>

<ul>
  <li><strong>Environment.</strong> Android Studio generated a project that immediately failed to build. A Gradle plugin version didn’t exist. My corporate VPN was silently blocking downloads via SSL inspection—🤫. The Wear OS template ships with the wrong UI library. The app crashed on first launch for missing permissions that the compiler never warned me about. None of these are code problems—they’re platform faffing. A co-pilot helps you diagnose them, but you have to know the right questions to ask.</li>
  <li><strong>UX decisions.</strong> The Foursquare API treats the radius parameter as a suggestion, not a hard limit—venues were populating 8km away when I’d asked for 300 meters. That’s a product decision dressed up as a technical one: do you trust the API’s ranking, or enforce your own filter? On a tiny watch screen where you can see maybe five venues, the answer is obvious—but a co-pilot doesn’t have the product context to make that call for you.</li>
  <li><strong>OAuth on a browserless device.</strong> The app needs Foursquare authentication, which means OAuth, which means a browser. Wear OS doesn’t have one. My first design had a token field where I banged in 48 characters on the watch keyboard—either comical or infuriating depending on your mood. Process of elimination led me to a companion phone app: it opens a Chrome Custom Tab, intercepts the OAuth redirect via authorization code flow, and sends the token to the watch via Bluetooth.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> Minimal, one-time setup. This is the kind of problem that can’t be vibe-coded. No amount of prompting produces the right architecture—you have to understand the constraints of the device and the expectations of the user.</li>
</ul>

<p>Vibe-coding scales to unfamiliar platforms as long as you can identify and decompose the problem. The AI handles syntax and boilerplate. So it seems the hard parts of building have shifted from the code (implementation) to everything else: environment, architecture, product judgment, platform constraints.</p>

<p>The whole thing—from opening Android Studio for the first time to checking into a Foursquare venue from my watch—took about an afternoon (a.k.a. naptime) to get sorted.</p>

<p><img src="/assets/og/post_pixel.watch.swarma.png" alt="Swarma on Pixel Watch" style="width: 70%;" /><span class="muted small">Swarma v1.0, now in closed testing on the Play Store.</span></p>

<p>Swarm lives on the Pixel Watch 🤘</p>

<p>— ᴘ. ᴍ. ʙ.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Registered mark pending. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>The first version of the companion app used a WebView instead of a Chrome Custom Tab—meaning users were authenticating inside my app rather than on Foursquare’s own site. A tester on the Wear OS subreddit (<a href="https://www.reddit.com/user/sorross/">u/sorross</a>) flagged it immediately. Took an hour to fix. The co-pilot had taken the path of least resistance; a human caught it. Which is probably the other lesson here. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Paul Berens</name></author><category term="tech" /><category term="product" /><summary type="html"><![CDATA[What vibe-coding a wearable app taught me about where the hard parts have shifted.]]></summary></entry><entry><title type="html">Desirable Difficulties</title><link href="https://berens.co/spanish.html" rel="alternate" type="text/html" title="Desirable Difficulties" /><published>2026-02-27T00:00:00+00:00</published><updated>2026-02-27T00:00:00+00:00</updated><id>https://berens.co/spanish</id><content type="html" xml:base="https://berens.co/spanish.html"><![CDATA[<p>People are always surprised when they find out I speak Spanish. I don’t speak perfectly, but well enough to hold a real conversation, and well enough that occasionally someone asks <em>¿De qué país son tus padres?</em> Which is the real compliment.</p>

<p>But the more common question is <em>¿Cómo aprendiste el español?</em> and I find that question itself telling. In the US, while we all take language classes in school, virtually no one ends up even passably proficient…so much so that there’s not even an expectation. The richest country in the world, and we seem to have made our peace with monolingualism, conveniently blaming geography and the global dominance of English.</p>

<p>My path was accidental, and it started with my one-of-a-kind seventh-grade Spanish teacher, Sr. David Donch.</p>

<h2 id="el-señor-donch">El Señor Donch</h2>

<p>David Donch is a New Jersey native who joined the Peace Corps in his youth, ended up in Venezuela, and didn’t come back until he brought a beautiful Venezolana home with him. He was unconventional in the best sense: he threw out the textbook—which I’m sure cost him countless hours of curriculum development—and replaced it with vocab bingo cards and audio cassettes. (To this day I associate <em>la sal</em> and <em>el azúcar</em> together because they shared a bingo square.)</p>

<p>When I moved to a different school the following year, my new Spanish teacher was a <em>gringa</em> with a flat accent and a standard-issue textbook (“turn to page 78 for today’s lesson”). So Sr. Donch graciously agreed to tutor me, and he’d make the trek across St. Paul to our house. Then one day he mentioned he needed to repair the roof of his casita in the Venezuelan Andes, and suggested I (and a couple other students) come along to really learn the language. And do some fly fishing.</p>

<p>I was fourteen. My parents (amazingly) said yes. And it changed everything.</p>

<h2 id="my-month-in-venezuela-at-age-14">My Month in Venezuela at Age 14</h2>

<p>We landed in Maracaibo with duffel bags, my very first passport, and an English-Spanish dictionary. The first night we stayed with friends of Sr. Donch’s, and I sat there absorbing the language—or trying my best to—while missing the vast majority of it. Total sensory overload. Would I like something to drink? <em>¿Coca?</em> (that is, <em>Cocaine?</em>)—instead of <em>una Coca-Cola</em>. The place erupted. The heat rose in my cheeks as I laughed at myself…embarrassment is an extraordinarily effective teacher.</p>

<p>But no one tells you that you have to be willing to look like an idiot to learn a language. That there’s really no other path forward. Actually, you can extend that well beyond langauge to nearly anything. Shane Parrish puts it well: “we’re often unwilling to look foolish in the short term to succeed in the long term.” And that trade-off isn’t optional: you either make the mistakes or you don’t learn; full stop.</p>

<p>The other aspect of this was the fullness of the immersion. We spent the majority of the trip in the beautiful mountainous state of Trujillo, where very few people spoke English—and Mr. Donch was pretty good about not bailing me out. It was a full commit, which was hard at first. But I wasn’t placed with a host family where English crept back in at the dinner table, nor was I surrounded by fellow English speakers the way so many study abroad programs inadvertently arrange things…where your native tongue becomes a constant refuge and the whole point is quietly defeated.</p>

<h2 id="the-mechanics">The Mechanics</h2>

<p>Looking back, a few things stand out that I think generalize beyond Spanish:</p>

<p>You have to let language wash over you rather than chase every word. If you try to translate everything in real time, you’ll fall behind and miss what comes next. But here’s the thing: we naturally do this in our native language without realizing it. Have you ever been half-listening to someone and suddenly gotten caught off guard by an unexpected turn of phrase (because you’d already mentally finished their sentence for them)? That’s <strong>collocation</strong>: the phenomenon where certain words reliably travel together, and a fluent brain learns to anticipate the pairing. Salt pulls pepper; trial pulls error. In corpus linguistics it’s defined as words that co-occur more often than chance would predict, but experientially it’s just what normal fluency feels like. When you’re learning a new language, and you don’t have those grooves yet, every phrase is a surprise, which is why the immersion strategy works: you’re forcing those associations at speed. At some point you stop translating and just start anticipating, which is how and when things unlock.</p>

<p><strong>Accent matters</strong> more than you’d think, and for an unexpected reason. I ended up sounding reasonably native—partly, I think, because of an ear trained by music and years of mimicking sounds—while having vocabulary gaps that a six-year-old wouldn’t have. But the effect was that native speakers engaged with me <em>as if</em> I knew what I was doing. They didn’t switch to English out of pity but rather kept going. Compare this to others I know who have impeccable grammar (correct tenses and conjugation, etc.), but comically American accents: native speakers jump to bail them out, which is nice but of course hinders improvement. The friction I got from <em>sounding</em> like I belonged was itself a form of instruction.</p>

<p>Memory works through collision and surprise. <em>Sal</em> and <em>azúcar</em> are fused in my brain because they bumped into each other on that bingo card. The <em>cocaína</em> incident is seared in because it was a learning moment. As I understand it, these are what cognitive science researchers call <strong>desirable difficulties</strong>, the counterintuitive finding that harder, less comfortable learning conditions produce better long-term retention than easy, smooth ones. Fluency isn’t built through frictionless repetition; it’s built through the moments that make you catch yourself and recalibrate.</p>

<h2 id="the-ai-question">The AI Question</h2>

<p>Which brings me to the thing I keep thinking about.</p>

<p>AI is close to making real-time translation seamless. In the very near-term—I don’t want to be in the game of AI prediction—you’ll be able to speak to anyone in any language and be understood, without ever having learned a word. Which raises the obvious question: does learning a language even matter anymore?</p>

<p>But the better question is what AI does to the process of learning itself.”</p>

<p>Done right, an AI tutor could be extraordinary: infinitely patient, available at 01:00, able to accommodate your precise needs relative to where you are. Conversational AI built specifically to teach could replicate the immersive pressure I felt in Trujillo without requiring a plane ticket.</p>

<p>Some learners will use it this way, but the temptation runs the other direction: why struggle to produce a sentence when the AI will just produce it for you? Why tolerate the embarrassment of the wrong word when you can simply not speak and let the tool speak for you? The risk is that AI removes friction entirely for everyone, regardless of style, if we let it.</p>

<p>And here’s what I keep coming back to: even if tinkerers and systematizers learn differently, even if some people need structure and others need to poke at things until they yield, I suspect there’s a floor of friction below which nobody actually learns. The form of difficulty may vary. But difficulty itself doesn’t.</p>

<p>Sr. Donch intuitively understood this, which is why he deliberately didn’t make things easy. He made things interesting, and then he made things hard in the right ways: bingo cards as a mnemonic device, a total-immersion trip with no English escape hatch.</p>

<p>The question for anyone building AI tools is whether we’re designing for that same principle, or whether we’re quietly engineering away the very resistance that produces learning.</p>

<p>¿Entendiste, pues?</p>

<p>— ᴘ. ᴍ. ʙ.</p>]]></content><author><name>Paul Berens</name></author><category term="culture" /><category term="american identity" /><summary type="html"><![CDATA[Learning a language as a template for learning anything]]></summary></entry><entry><title type="html">Claude Code, Dark Mode</title><link href="https://berens.co/dark-mode.html" rel="alternate" type="text/html" title="Claude Code, Dark Mode" /><published>2026-01-23T00:00:00+00:00</published><updated>2026-01-23T00:00:00+00:00</updated><id>https://berens.co/dark-mode</id><content type="html" xml:base="https://berens.co/dark-mode.html"><![CDATA[<p>Ladies and Gentlemen, at long last…by popular demand…I give you…</p>

<p><strong><em>…dark mode on berens.co</em></strong><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> [Cue rampageous applause.]</p>

<p><img src="/assets/og/post_dark_mode.png" alt="Introducing Dark Mode on berens.co" />
<span class="muted small">Dark Mode insisted on a lifestyle photoshoot before going live.</span></p>

<h2 id="clarification">Clarification</h2>

<p>Hold up. <em>(Screeching brakes.)</em></p>

<p>This post isn’t so much about the wonders of dark mode on my website as about <em>how it was achieved</em>.</p>

<h2 id="background">Background</h2>

<p>Last October I chronicled my experience <a href="/vibe-coding.html">“vibe coding”</a>—building things through AI-assisted trial and error without formal programming knowledge—and then again in November on how I used an LLM to spin up <a href="/song-bot.html">my first bot</a>. That workflow looked like this: describe what I want → Claude generates code snippets → I copy the code → I paste it into the right file → I save the file → I commit to git → I push to GitHub → the site rebuilds → I test it → if it fails, I report the error and repeat.</p>

<p>It worked, but it was pretty manual and the onus was on me to move code around and ensure everything ends up in the right place.</p>

<p>Then I discovered <strong>Claude Code</strong> and old-school vibe coding instantly became dial-up internet.</p>

<p>For those unfamiliar, Claude Code is a command-line tool that connects to your GitHub repository and can read, write, debug, and test code directly in your codebase—all through natural language commands. Basically your own AI coding assistant or “pair programmer”.</p>

<h2 id="the-dark-mode-case-study">The Dark Mode Case Study</h2>

<p>I needed a project to test drive it: how about building dark mode, everyone’s favorite UI enhancement. I pointed it at berens.co, authenticated, and it was ready to work directly in my codebase.</p>

<p>In the old vibe coding workflow, this would have meant dozens of copy-paste cycles across multiple files—CSS variables, media queries, template updates, testing each piece. Instead, I opened Claude Code and typed: “what’s the simplest way to implement a ‘dark mode’ on my current setup; one that inherits the ‘system preference’ of the browser?”</p>

<p>Claude Code didn’t just spit out some generic code. It read my entire site structure to understand the setup, analyzed my current CSS file, then converted all hardcoded colors to CSS custom properties. It created a complete <code class="language-plaintext highlighter-rouge">@media (prefers-color-scheme: dark)</code> section with a carefully chosen dark color palette, added the proper meta tag to my HTML header, tested the implementation, and committed everything to a new git branch.</p>

<p><strong>My role?</strong> Overbearing supervisor and critic. I looked at the preview and said “here’s a better color palette” (showing a screenshot). Claude Code adjusted. I mentioned “the table headers need work”—it read <code class="language-plaintext highlighter-rouge">pages/today.md</code>, identified the issue, and fixed it. Back and forth we went, refining details:</p>

<ul>
  <li>“The input boxes should be darker”</li>
  <li>“The highlighting is too faint”</li>
  <li>“Kill those right borders”</li>
  <li>“Make the left border invisible unless it’s highlighting”</li>
  <li>“Do me a favor and lose five pounds immediately or get out of my building like now!”</li>
</ul>

<p>Each time, Claude Code read the relevant files, understood the context, made the changes, tested them, and committed them. I never opened my code editor nor touched git. I just hollered out what I wanted.</p>

<p><img src="/assets/og/post_claude_code.png" alt="Claude Code doing its thing" />
<span class="muted small">Claude Code taking the wheel.</span></p>

<h2 id="this-is-agentic-ai">This Is Agentic AI</h2>

<p>This was my first real experience with an <strong>agentic AI</strong>—an AI that doesn’t just generate outputs for you to handle, but actually takes actions in your environment.</p>

<p>The difference is astounding. With my original vibe coding, “Advisory Claude” generates the code; whereas here Claude Code actually builds the feature itself. Advisory Claude gives me the recipe; Claude Code cooks dinner. Advisory Claude gives me directions; Claude Code is the autonomous vehicle that drives us there.</p>

<p>It’s not that one is smarter than the other—they’re using the same underlying model—it’s that Claude Code has <em>agency</em> in that it can read files, edit them, run tests, commit changes. It operates in my development environment like a junior developer who’s skilled at interpreting my terrible, inexact prompts.</p>

<h3 id="near-zero-learning-curve">Near-Zero Learning Curve</h3>

<p>Because I was already fluent in traditional vibe coding—hey, I should add that to my résumé—there’s really no ramp to Claude Code. It’s essentially the same process:</p>
<ul>
  <li>Describe desired outcomes clearly</li>
  <li>Provide visual feedback (screenshots)</li>
  <li>Iterate on details</li>
  <li>Recognize when something isn’t quite right</li>
</ul>

<p>So it’s the same process as with Advisory Claude; the only difference was that now I didn’t have to be the one moving the pieces around.</p>

<h2 id="what-this-means">What This Means</h2>

<p>The barrier to building things is shifting from “can I figure out how to code this?” to “can I convey what I want?” If you’ve been vibe coding you’re already positioned for this shift.</p>

<p>For most, the future isn’t about becoming a traditional developer. It’s about being able to articulate what you want to build, recognize when it’s right, and iterate until it works.</p>

<p>Feels about right to me: the computer handles syntax, file structure, and other minutiae; the humans worry about design, taste, and imagination.</p>

<p>But I, for one, welcome our agentic overlords.</p>

<p>— ᴘ. ᴍ. ʙ.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Still seeing light mode? That’s because your system or browser is set that way—the <a href="/colophon/">colophon</a> shows what we detected. Flip the preference in your OS or browser and the page will update right away. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Paul Berens</name></author><category term="tech" /><category term="product" /><summary type="html"><![CDATA[Hey, Claude Code, make me a sandwich!]]></summary></entry><entry><title type="html">The Donor-Advised Fund</title><link href="https://berens.co/daf.html" rel="alternate" type="text/html" title="The Donor-Advised Fund" /><published>2025-12-26T00:00:00+00:00</published><updated>2025-12-26T00:00:00+00:00</updated><id>https://berens.co/daf</id><content type="html" xml:base="https://berens.co/daf.html"><![CDATA[<p>This was us every year up ‘til now (tell me if this is familiar): the end of the year rolls around, and amidst all the Christmas hustle and bustle, we suddenly realize we need to make our charitable giving decisions and contributions before 31 December. And so we find ourselves hastily “peanut buttering” donations across various organizations—a little here, a little there—just to add up to a certain target dollar amount and in time to get processed before the new year.</p>

<p>More financial triage than intentional charitable giving.</p>

<h2 id="enter-donor-advised-fund-daf">Enter donor-advised fund (DAF)</h2>

<p>A DAF changes this dynamic: you contribute money (or stock) to the fund and receive an immediate tax deduction. The funds are invested and grow tax-free. Then you distribute “grants” to charities on your own timeline, which could be next month but it could also be a decade from now.</p>

<p>You’ve effectively separated the tax break from the actual giving.</p>

<h2 id="benefits">Benefits</h2>

<p><img src="/assets/og/post_daf.jpg" alt="Sell me this DAF" /></p>

<ol>
  <li><strong>Tax efficiency</strong>: Take the deduction when it makes sense for your tax situation; not necessarily when you’re making a charitable contribution.</li>
  <li><strong>Administrative simplicity</strong>: Make one contribution to your DAF and get one tax receipt, then distribute to multiple charities over time without additional tax paperwork.</li>
  <li><strong>Thoughtful giving</strong>: You can research organizations, respond to real needs as they arise (or pause as you anticipate some greater future need), and give with a bit more intentionality now that you’ve removed the time pressure. (More on intentional giving in <a href="/philanthropy.html">this post</a>.)</li>
  <li><strong>Portfolio optimization</strong>: DAFs are ideal for donating appreciated securities, turning portfolio cleanup into charitable impact while maximizing tax benefits.
    <ul>
      <li><em>Practical example:</em> We recently used our DAF to donate a good amount of highly appreciated individual stocks (some with 400%+ gains), which killed three birds with one stone: eliminated a tangled mess of 22 tax lots we’d been wanting to consolidate, avoided a boatload of embedded capital gains tax, and generated a separate boatload in total tax savings whilst pre-funding a healthy runway of charitable giving.</li>
    </ul>
  </li>
  <li><strong>Growth potential</strong>: Charitable dollars grow tax-free before you distribute them, potentially (hopefully) increasing your total charitable impact.</li>
  <li><strong>Anonymous giving</strong>: Most DAF providers allow you to give anonymously—the charity sees the payment from the sponsor (e.g., “Vanguard Charitable”) without your identifying information. While public giving has its place, anonymous giving offers benefits around humility, self-effacement, privacy, and avoiding inequality dynamics.</li>
</ol>

<h2 id="arent-dafs-for-the-ultra-wealthy">Aren’t DAFs for the ultra-wealthy?</h2>

<p>Well that there’s a big misconception, my friend. They’re more accessible than you’d think with many providers<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> setting initial contributions at or below what many families already give to charity annually, and even others offering accounts with zero minimum. Basically, you might already be giving enough to make a DAF worthwhile—or could be, Mr. and Mrs. Scroogie McScroogeface.</p>

<p>Think of a DAF as sitting alongside your other tax-advantaged accounts:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Purpose</th>
      <th style="text-align: left">Tool</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Retirement</td>
      <td style="text-align: left">401(k), IRAs, etc.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup></td>
    </tr>
    <tr>
      <td style="text-align: left">Education</td>
      <td style="text-align: left">529</td>
    </tr>
    <tr>
      <td style="text-align: left">Healthcare</td>
      <td style="text-align: left">HSA</td>
    </tr>
    <tr>
      <td style="text-align: left">Emergency fund</td>
      <td style="text-align: left">HYSA</td>
    </tr>
    <tr>
      <td style="text-align: left"><em>Philanthropy</em></td>
      <td style="text-align: left"><em>Donor-advised fund</em></td>
    </tr>
  </tbody>
</table>

<p>If you’ve ever felt the aforementioned year-end crunch, and if you’d like to be more purposeful with your philanthropy, a donor-advised fund might be just the thing that’s missing from your financial quiver<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>.</p>

<p>And Happy Boxing Day—how <em>apropos</em>,</p>

<p>— ᴘ. ᴍ. ʙ.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>We went with <a href="https://www.vanguardcharitable.org/">Vanguard Charitable</a>, which is sort of a no-brainer if you’re using Vanguard for your regular portfolio. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>I’ll uncover the wonders of the (surprisingly legal) Backdoor Roth Conversion in a sleep-inducing future post. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>Be sure to mention code “Berens” when you sign up—<em>CALL NOW!</em> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Paul Berens</name></author><category term="personal finance" /><summary type="html"><![CDATA[The financial tool you're overlooking]]></summary></entry><entry><title type="html">Song Bot</title><link href="https://berens.co/song-bot.html" rel="alternate" type="text/html" title="Song Bot" /><published>2025-11-21T00:00:00+00:00</published><updated>2025-11-21T00:00:00+00:00</updated><id>https://berens.co/song-bot</id><content type="html" xml:base="https://berens.co/song-bot.html"><![CDATA[<p>I continue my <a href="/vibe-coding">vibe coding</a> escapade with my first bot: <a href="https://t.me/pb_song_bot" target="_blank">a song-of-the-day Telegram bot</a> that automatically sends a tasty track to subscribers every morning to enjoy with coffee.</p>

<h2 id="first-the-music">First the Music</h2>
<p>Before any bot building, I curated a set of my favorite songs of all time—a revelatory exercise that clarified my tastes: lots of classic rock and folk-rock from the ’60s-‘80s golden era, plus generous helpings of singer-songwriters, soul/R&amp;B, and live tracks. Christmas music (both sacred and secular) bookends the calendar from early December through early January, confirming what I <a href="/xmas-music">previously discussed</a>: I do love the season. I aimed for artist diversity (no more than six tracks each) and gravitate toward deep cuts—so if you recognize a hit here, it’s because it’s genuinely great. But as Grandpa Brennan was wont to say, <em>de gustibus non est disputandum.</em></p>

<h2 id="bot-building">Bot Building</h2>
<p>This was a product of Claude (Sonnet 4.5). Admittedly, it didn’t get everything perfect on the first pass, but it sure took my abrasive feedback in stride. The process wasn’t totally linear, but it went something like this:</p>
<ol>
  <li>Learn about Telegram bots</li>
  <li>Set up and configure the bot</li>
  <li>Create the initial daily song workflow</li>
  <li>Debug some “ES module” issues<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">1</a></sup></li>
  <li>Add self-service subscriptions</li>
  <li>Test and fix</li>
  <li>Refine (message formatting, etc.)</li>
</ol>

<p><img src="/assets/og/post_telegram-workflow.png" alt="Failing Telegram workflows" style="width: 70%;" /><span class="muted small">Fail, fail, fail, success, fail, fail...</span></p>

<h2 id="the-setup">The Setup</h2>
<p>Here’s what I ended up with:</p>
<ol>
  <li><strong>Telegram Bot</strong> – Created via <a href="https://t.me/BotFather@BotFather" target="_blank">@BotFather</a> w/ token stored as GitHub secret. Users subscribe themselves.</li>
  <li><strong>Data Source</strong> (<code class="language-plaintext highlighter-rouge">_data/daily_song.yml</code>) – My impeccably-curated list of 366 songs<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">2</a></sup> with date, track title, artist, and YouTube Music song ID.
    <pre><code class="language-YAML">  - date: "12-09"
    track: "Christmas Is Coming"
    artist: "Vince Guaraldi Trio"
    songId: "2--a88MKHZc"
</code></pre>
  </li>
  <li><strong>GitHub Actions Workflows</strong>
    <ul>
      <li><strong>Workflow #1: Subscription Manager</strong> (<code class="language-plaintext highlighter-rouge">telegram-subscriptions.yml</code>) – Runs daily at 07:00 Pacific, and checks for <code class="language-plaintext highlighter-rouge">/subscribe</code> and <code class="language-plaintext highlighter-rouge">/unsubscribe</code> messages, adding/removing users to/from <code class="language-plaintext highlighter-rouge">_data/telegram_subscribers.json</code>, kicks out confirmation messages.</li>
      <li><strong>Workflow #2: Daily Song Sender</strong> (<code class="language-plaintext highlighter-rouge">telegram-daily-song.yml</code>) – Runs a half hour later (~07:45), reading today’s song from YAML, sending the selection with clickable link to the track on YouTube Music to all subscribers in the JSON file:</li>
    </ul>
  </li>
  <li><strong>Rinse and repeat</strong> – Repeats daily, fully automated via GitHub Action with no servers or hosting costs.</li>
</ol>

<p><img src="/assets/og/post_song.bot.343.png" alt="bot notification" /></p>

<h2 id="youre-lookin-at-a-few-options-here">You’re lookin’ at a few options here</h2>
<ol>
  <li><a href="https://t.me/pb_song_bot" target="_blank">Subscribe</a> so that we can know the joy of experiencing the same songs together on the very same days. (Then just sneak out with an <code class="language-plaintext highlighter-rouge">/unsubscribe</code> if you’re not feelin’ my jams.)</li>
  <li>Get the same song of the day via the <a href="/today/">today</a> page if you’re more of <em>pull</em> person than a <em>push</em> person.</li>
  <li>Make your own SOTD bot by cloning these workflows and <code class="language-plaintext highlighter-rouge">.yml</code> files from <a href="https://github.com/berensp/berensp.github.io/">my GitHub repo</a>. I’ll subscribe.</li>
</ol>

<h2 id="bottom-line">Bottom line</h2>
<p>It’s astonishing that a non-coder like moi can go from idea to fully functional Telegram bot with auto-subscriptions in one sub-100-message chat session—that there’s some right-fine vibe codin’…at least for the standards of late 2025 (I’m sure it’ll seem quaint in a year or two).</p>

<h2 id="update-may-2026">Update (May 2026)</h2>
<p>Six months in, two problems surfaced: the song was arriving noticeably late and at inconsistent times. Turned out there were two culprits conspiring together: 1) GitHub’s automation scheduler having no awareness of local time or seasonal clock changes, and 2) GitHub’s scheduler not being very punctual, its automations more like suggestions than alarms (ergo day-to-day randomness). So I swapped GitHub’s internal scheduler for <a href="https://cron-job.org" target="_blank">cron-job.org</a>, a dedicated (and free) scheduling service that pings GitHub at exactly the right local time each day (even accounting for DST) and says “run the song workflow now, Bro”. Delivery is now consistent to within a minute. The whole diagnosis-and-fix took ~20 minutes with Claude (Sonnet 4.6).</p>

<p>— ᴘ. ᴍ. ʙ.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:2" role="doc-endnote">
      <p>This is the type of arcana that’s over my head and so I’m totally reliant on the LLM to diagnose and fix: Node.js was trying to run our script as an ES Module (basically new JavaScript) (which doesn’t allow <code class="language-plaintext highlighter-rouge">require()</code>), but the thing was written in old Javascript (i.e., CommonJS style), so got this error about “require is not defined in ES module scope.” So the fix—after a number of back-and-forths—was renaming the file to <code class="language-plaintext highlighter-rouge">.cjs</code> (CommonJS file extension) and wrapped the <code class="language-plaintext highlighter-rouge">await</code> in an async function, which told Node: “Hey, this is old-style CommonJS, not modern ES Modules.” 🤓 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>Yes, leap year support built in. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Paul Berens</name></author><category term="tech" /><category term="music" /><category term="product" /><summary type="html"><![CDATA[🤖]]></summary></entry><entry><title type="html">Vibe Coding</title><link href="https://berens.co/vibe-coding.html" rel="alternate" type="text/html" title="Vibe Coding" /><published>2025-10-28T00:00:00+00:00</published><updated>2025-10-28T00:00:00+00:00</updated><id>https://berens.co/vibe-coding</id><content type="html" xml:base="https://berens.co/vibe-coding.html"><![CDATA[<p>For those of you who have been with us over the years (read: Yeobo), you’ve seen <a href="/">this site</a> become increasingly sophisticated—not through any formal training of mine, but through “vibe-coding”: learning through AI-powered trial and error.</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">There&#39;s a new kind of coding I call &quot;vibe coding&quot;, where you fully give in to the vibes, embrace exponentials, and forget that the code even exists. It&#39;s possible because the LLMs (e.g. Cursor Composer w Sonnet) are getting too good. Also I just talk to Composer with SuperWhisper…</p>&mdash; Andrej Karpathy (@karpathy) <a href="https://twitter.com/karpathy/status/1886192184808149383?ref_src=twsrc%5Etfw">February 2, 2025</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<h2 id="forking-and-fumbling">Forking and Fumbling</h2>

<p>Back in 2019 or early 2020, I forked <a href="https://danromero.org">Dan Romero’s Jekyll site</a> and deployed it to GitHub Pages. Jekyll was appealing because it was simple: write in Markdown, commit to GitHub, and your site updates automatically—<a href="/this-site">no servers, no databases, no complexity</a>.</p>

<p>I meant to keep things totally simple and just use it for <a href="/posts/">blog posts</a>, and for a short while I did, but at my core I’m a <a href="/learning/">tinkerer</a>—and a technophile at that—so this site ended up becoming my experimentation canvas.</p>

<h3 id="2019-2022-the-google--stack-overflow-era">2019-2022: The Google + Stack Overflow Era</h3>
<p>The early days of this site were pre-LLM boom, so every problem involved Google queries which led me to Stack Overflow threads, which led me to half-solution code snippets I didn’t fully understand but could adapt. Slowly but surely, I added small bits of functionality like time and date calcs, dynamic content displays, etc. Progress was slow but educational.</p>

<h3 id="2023-the-chatgpt-experiment">2023: The ChatGPT Experiment</h3>
<p>After ChatGPT went public in late 2022, I tried using it for coding help. It was hit or miss, though typically more miss. In this era there was a good amount of “LLM as the confident BSer,” which is amusing…for a short while. So I upgraded to “ChatGPT Plus” almost exactly two years ago (October 2023) in the hopes of some improvement, but alas, the code would look okay, but ultimately error out. I’d go back to Stack Overflow. Or just have a total dead end.</p>

<h3 id="2024-the-claude-breakthrough">2024: The Claude Breakthrough</h3>
<p>I tried Claude by Anthropic (the free version initially) and something clicked. When code failed, I’d paste the error message and Claude would often spot the issue and fix it—not all the time; but an impressive batting average. On the third or fourth time I found myself reflexively exclaiming “you got it, you genius SOB!” I canceled my ChatGPT subscription and upgraded Claude account to Pro.</p>

<h2 id="fetching-and-refreshing">Fetching and Refreshing</h2>

<p>I had looked into exploiting APIs a couple years ago (must’ve been in my ChatGPT Era), but it was too complex for this muggle. Time passed and with it step change improvements with each LLM (less context required, higher hit rates).</p>

<p>Recently, I thought: <em>hey, now that we’re living in the future (here on Claude Sonnet 4.5<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>), this is maybe the moment to give this API thing another go.</em> I started with <strong>Swarm</strong> (which you remember as Foursquare—yes, I still use as a lifelogging tool). It ended up being pretty painless: a GitHub Actions workflow that runs nightly, calls the Foursquare API to grab my latest data, saves it to a .json file in my repo, and commits the change—which triggers Jekyll to rebuild my site with the fresh data.</p>

<p>Integrating <strong>Strava</strong>, the GPS-based social network for athletes, was more complex because their API requires OAuth with refresh tokens that expire every six hours or something like that. So I had the same GitHub Actions workflow but then it needed to see if my Strava token was still valid, refresh it automatically if it’s expired, and then fetch most recent data and save to a .json file, etc.</p>

<p>When it fired correctly and token refresh happened automagically…elation. And it left me in awe that something that used to be so inaccessible was now at my fingertips.</p>

<p>To illustrate what “vibe-coding” looks like in practice, here’s the trickiest piece of code we built—the Strava token refresh logic:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Function to refresh access token</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">refreshAccessToken</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Refreshing access token...</span><span class="dl">'</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">postData</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">({</span>
    <span class="na">client_id</span><span class="p">:</span> <span class="nx">clientId</span><span class="p">,</span>
    <span class="na">client_secret</span><span class="p">:</span> <span class="nx">clientSecret</span><span class="p">,</span>
    <span class="na">refresh_token</span><span class="p">:</span> <span class="nx">refreshToken</span><span class="p">,</span>
    <span class="na">grant_type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">refresh_token</span><span class="dl">'</span>
  <span class="p">}).</span><span class="nx">toString</span><span class="p">();</span>
  
  <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">hostname</span><span class="p">:</span> <span class="dl">'</span><span class="s1">www.strava.com</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/oauth/token</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
      <span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/x-www-form-urlencoded</span><span class="dl">'</span><span class="p">,</span>
      <span class="dl">'</span><span class="s1">Content-Length</span><span class="dl">'</span><span class="p">:</span> <span class="nx">postData</span><span class="p">.</span><span class="nx">length</span>
    <span class="p">}</span>
  <span class="p">};</span>
  
  <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">makeRequest</span><span class="p">(</span><span class="nx">options</span><span class="p">,</span> <span class="nx">postData</span><span class="p">);</span>
  
  <span class="k">if</span> <span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">access_token</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">accessToken</span> <span class="o">=</span> <span class="nx">response</span><span class="p">.</span><span class="nx">access_token</span><span class="p">;</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Access token refreshed successfully</span><span class="dl">'</span><span class="p">);</span>
    
    <span class="c1">// Update GitHub secret with new access token</span>
    <span class="k">try</span> <span class="p">{</span>
      <span class="nx">execSync</span><span class="p">(</span><span class="s2">`gh secret set STRAVA_ACCESS_TOKEN --body "</span><span class="p">${</span><span class="nx">accessToken</span><span class="p">}</span><span class="s2">"`</span><span class="p">,</span> <span class="p">{</span>
        <span class="na">env</span><span class="p">:</span> <span class="p">{</span> <span class="p">...</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">,</span> <span class="na">GH_TOKEN</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">GH_TOKEN</span> <span class="p">}</span>
      <span class="p">});</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">GitHub secret updated</span><span class="dl">'</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="dl">'</span><span class="s1">Warning: Could not update GitHub secret:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">);</span>
    <span class="p">}</span>
    
    <span class="k">return</span> <span class="nx">response</span><span class="p">.</span><span class="nx">access_token</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Failed to refresh token: </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">response</span><span class="p">));</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Could I have written this from scratch? Negative, Ghost Rider. But I can suss out what it does:</p>
<ol>
  <li>Makes a POST request to Strava with the refresh token</li>
  <li>Gets back a new access token</li>
  <li>Updates the GitHub secret so it has the fresh token next time it runs</li>
  <li>Handles errors with style and grace</li>
</ol>

<p>The nice part of this setup is how simple it is architecturally:</p>
<ul>
  <li><strong>Jekyll data files</strong> (<code class="language-plaintext highlighter-rouge">_data/*.json</code>) can be referenced in templates using Liquid syntax</li>
  <li><strong>GitHub Actions</strong> can run Node.js scripts on a schedule</li>
  <li><strong>API data</strong> gets committed to the repo, triggering automatic site rebuilds</li>
</ul>

<p>No servers to maintain, no databases to manage, no hosting costs. Just a static site that happens to have dynamic data.</p>

<h2 id="the-upshot">The Upshot</h2>
<ul>
  <li><strong>Static sites can be a <em>little</em> dynamic.</strong> With the right architecture, a Jekyll site can pull live data and feel almost real-time, despite being fundamentally static.</li>
  <li><strong>We’re squarely in the low-code era.</strong> Refresh tokens, authorization codes, GitHub Actions—what was once pretty arcane territory is fair game now that I’ve actually worked through it (and by “worked through it” of course I mean “supervising the AI that worked through it”).</li>
  <li><strong>Check your imposter syndrome at the door.</strong> You just need curiosity, patience, and a good AI conversation partner. Because you’re good enough, smart enough, and gosh darnit, people like you.</li>
</ul>

<p>For my web savvy friends, I do realize this is not a technical marvel. I’m basically a three-year-old who’s thrilled with his creation, and so you (the mom or dad) need to look past the anatomical peculiarities and just beam with pride.</p>

<p><img src="/assets/og/post_vibe-drawing.png" alt="vibe-drawing" /></p>

<p>And that’s okay, because I quite like this little project that I’ve vibed into existence.</p>

<p>Excelsior,</p>

<p>— ᴘ. ᴍ. ʙ.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Just noting this for posterity. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Paul Berens</name></author><category term="tech" /><summary type="html"><![CDATA[WAGMI]]></summary></entry><entry><title type="html">King Arthur and His Knights of the Round Table</title><link href="https://berens.co/king-arthur.html" rel="alternate" type="text/html" title="King Arthur and His Knights of the Round Table" /><published>2025-09-15T00:00:00+00:00</published><updated>2025-09-15T00:00:00+00:00</updated><id>https://berens.co/king-arthur</id><content type="html" xml:base="https://berens.co/king-arthur.html"><![CDATA[<p>Loved it. M. and I took our time with <em>King Arthur</em>; he keeping all the different knights and their stories straight in his head.</p>

<p>And surprisingly Catholic. The Holy Grail features prominently; the knights gatherings seem to revolve around Pentecost, Christmas, and Michaelmas; and spiritual purity and Christian ideals are bigger themes than I realized. The ending (epilogue, I think) is also unexpected but very impressive for a Christian/Catholic.</p>

<blockquote>
  <p>‘Him do we serve indeed,’ said the knight, crossing himself reverently. ‘And so also do all men who live truly in this the realm of Logres. But on earth we serve His appointed Emperor – the noble King Arthur, at whose Round Table we sit.’</p>
</blockquote>

<p>I knew it had really sunk in / struck a chord when he started telling me his own story one night during bedtime, and at one point says as earnestly as ever “…on the morrow,…”.</p>

<p>— ᴘ. ᴍ. ʙ.</p>]]></content><author><name>Paul Berens</name></author><category term="theology" /><category term="books" /><summary type="html"><![CDATA[Loved it. M. and I took our time with King Arthur; he keeping all the different knights and their stories straight in his head.]]></summary></entry><entry><title type="html">Labor-Capital Disconnect</title><link href="https://berens.co/labor.html" rel="alternate" type="text/html" title="Labor-Capital Disconnect" /><published>2025-09-01T00:00:00+00:00</published><updated>2025-09-01T00:00:00+00:00</updated><id>https://berens.co/labor</id><content type="html" xml:base="https://berens.co/labor.html"><![CDATA[<p>Labor Day is one of those holidays where if you stopped the average person on the street, they might struggle to explain its meaning. To refresh: it originated from the fight for the eight-hour workday and better working conditions in the late 19th century. Over time, it evolved into a broader celebration of workers’ contributions to American prosperity. Which makes today a little ironic and/or outdated when labor seems increasingly disconnected from the wealth it’s meant to create: stock valuations soar whilst wages stagnate and unemployment rises—a seemingly impossible combo when we assumed prosperity flowed to both labor and capital.</p>

<p>The empirical foundation of labor-capital disconnection is the “Great Decoupling”<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> which describes the post-1970s divergence between productivity, GDP growth, and median wages; but you know it better as the <a href="https://wtfhappenedin1971.com/">wtfhappenedin1971.com</a> meme:</p>

<p><img src="/assets/og/post_wages.jpg" alt="Real GDP, Real Wages and Trade Policies in the U.S.: 1947-2014" /></p>

<h2 id="wherefore-the-disconnection">Wherefore the disconnection?</h2>

<p>Plenty of factors influence valuations and the capital markets, but some of the reasons labor plays a diminishing role:</p>
<ol>
  <li><strong>Technological leverage</strong> – software and automation allow massive scalability without proportional labor increases (e.g., one engineer’s code can serve millions)</li>
  <li><strong>Network effects</strong> – (digital) platforms become more valuable as more users join, leading to market dominance with small, highly-skilled workforces<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>.
    <ul>
      <li>And/or allowing said companies the surplus to create “b.s. jobs” (h/t David Graeber) (e.g., Twitter being able to let go three-quarters of its workforce and not feel an effect…although I’d also say that long-term hindsight may see the <a href="/cut-flowers.html">cut flowers effect</a> here)</li>
    </ul>
  </li>
  <li><strong>Global supply chains</strong> – allow companies to arbitrage labor costs worldwide while maintaining market power domestically, ensuring capital owners capture gains independent of local worker prosperity.</li>
  <li><strong>Financialization / financial engineering</strong> – e.g., stock buybacks, LBOs, other fancy financial instruments that boost share prices without necessarily improving real economic output.</li>
</ol>

<h2 id="ai-the-looming-accelerant">AI: The looming accelerant</h2>

<p>It’s still early innings, but AI has the potential to spell the most significant acceleration of labor-capital disconnection in history once adoption reaches scale. Recent empirical evidence suggests current job displacement effects remain modest<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>, but the structural conditions are being set for dramatic future impact. As AI adoption accelerates beyond the current 9% of businesses using it in production, we should expect wealth polarity to increase dramatically when AI separates those who can marshal the tools and benefits of AI from the have-nots (the ‘hollowing out’ hypothesis<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>). Of course, inequality is no new phenomenon (it’s a feature of a capitalism<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup>), but AI may be an accelerant for capital owners (where ownership is highly concentrated among a few major tech companies) and high-skill workers<sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">6</a></sup>.</p>

<p>What are we going to do about it? Some kind of restructuring of ownership / wealth redistribution (e.g., UBI, sovereign wealth funds)? Elite capture suggests probably not. Hopefully, we will at least reinvest in society by reimagining our education system, though this assumes the problem is skills mismatch rather than systemic capital concentration.</p>

<p>Of course, there will be shifts in labor toward AI resistant markets (e.g., plumbing, therapy, interior design, childcare), and current data suggests most workers aren’t yet seeing displacement—but while celebrating the worker today, we need to think about whether individual adaptation can address what may become a fundamental shift in how value gets created and distributed as AI adoption scales.</p>

<p>Happy Labor Day,</p>

<p>— ᴘ. ᴍ. ʙ.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Erik Brynjolfsson and Andrew McAfee, <em>Race Against the Machine: How the Digital Revolution Is Accelerating Innovation, Driving Productivity, and Irreversibly Transforming Employment and the Economy</em> (Lexington, MA: Digital Frontier Press, 2011). <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Nick Srnicek, <em>Platform Capitalism</em> (Cambridge: Polity Press, 2017). <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>Sarah Eckhardt and Nathan Goldschlag, “<a href="https://eig.org/ai-and-jobs-the-final-word/">AI and Jobs: The Final Word (Until the Next One)</a>,” Economic Innovation Group, August 10, 2025. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>Middle-skill jobs disappear while low-skill service jobs and high-skill knowledge work remain, creating labor market polarization. David H. Autor, Lawrence F. Katz, and Melissa S. Kearney, “The Polarization of the U.S. Labor Market,” <em>American Economic Review</em> 96, no. 2 (May 2006): 189-194. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">
      <p>Piketty’s r &gt; g formula: when the return to capital (r) exceeds economic growth (g), wealth concentrates among capital owners regardless of labor productivity. Thomas Piketty, <em>Capital in the Twenty-First Century</em>, trans. Arthur Goldhammer (Cambridge, MA: Belknap Press of Harvard University Press, 2014). <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6" role="doc-endnote">
      <p>There’s a labor economics theory called “skill-biased technological change” (SBTC) that explains how technology complements high-skill workers while substituting for others, creating winner-take-all dynamics that benefit capital and a small labor elite. <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Paul Berens</name></author><category term="economics" /><category term="tech" /><category term="first principles" /><category term="american identity" /><summary type="html"><![CDATA[Workin' nine to five. What a way to make a livin'.]]></summary></entry><entry><title type="html">Low-key Oenophilia</title><link href="https://berens.co/wine.html" rel="alternate" type="text/html" title="Low-key Oenophilia" /><published>2025-07-03T00:00:00+00:00</published><updated>2025-07-03T00:00:00+00:00</updated><id>https://berens.co/wine</id><content type="html" xml:base="https://berens.co/wine.html"><![CDATA[<p>Yesterday our family took our very first <a href="https://en.wikipedia.org/wiki/Pepsi_Challenge">Pepsi Challenge</a>—which happens to be 50 years old this year, by the way. And, sorry to brag here, but I confidently identified the Pepsi-Cola in all of its glorious superiority to Coca-Cola.</p>

<p>But wine? That’s a different story. I couldn’t identify a “note” of anything…but I’m going to claim that you likely can’t either.</p>

<p>In fact, our cola blind taste test reminded me of these wine taste tests I read about years ago in a piece that went viral on io9<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>:</p>
<ol>
  <li>Statistician Robert Hodgson presented blindfolded experts with identical wines three times—their ratings varied by ±4 points on a 100-point scale. Only 1 in 10 judges rated consistently, and those who seemed “consistent” one year performed randomly the next.</li>
  <li>Researcher Frédéric Brochet gave 54 experts two wines—one “red,” one white. Both were identical whites; the “red” was actually a white dyed with food coloring. Every expert described the fake red with typical red wine language (“jammy,” “crushed red fruit”). Not one noticed.</li>
  <li>When experts tasted identical Bordeaux from expensive vs. cheap bottles, they gave opposite descriptions—the “expensive” bottle was “complex” and “balanced” while the identical wine in the cheap bottle was “weak” and “flat.”</li>
  <li>A survey of 6,000+ blind tastings found that while wine experts enjoy pricier bottles more, amateur drinkers showed a <em>negative</em> correlation between price and enjoyment.</li>
</ol>

<p>And so it appears I have good reason for my suspicion of people who are (or claim to be) really into wine. Not because I have anything against wine itself—I enjoy it as much as the next person—but because for some, being “into” wine is a red flag of right proper toffery. I recognize some people are sincere and earnest wine lovers, but I’d wager that with a larger segment it’s an affectation<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>.</p>

<h2 id="tranquilo-hombre">Tranquilo, Hombre</h2>

<p>Over a decade ago there was a winery in the Dogpatch neighborhood of San Francisco called Sutton Cellars, where proprietor and winemaker Carl Sutton produced his own “natural” (unfiltered, no added yeast) wines. He’d buy grapes from Mendocino, Russian River, and other regions, experimenting freely—he loved Carignane, wasn’t shy about Grenache, and was obsessed with vermouth. The guy was serious and passionate about winemaking, but the best part was his utterly unserious approach to presenting it. His tasting room on the corner of 22nd &amp; Illinois was welcoming and relaxed, zero ego. The wine philosophy was simple: “What do you like to drink? Come check this out”—none of the “you should be detecting notes of blackcurrant, cedar, tobacco, and leather with hints of petrichor and crushed beetles” pretension you can find just 45 miles north in Napa.</p>

<p>Before that, at Michigan, we had the Wolverine Wine Club, which was supposed to introduce us uncouth MBAs to some refinement through good wine. But each event, no matter how elegant the venue, would devolve into utter debauchery. This so incensed the faculty advisor, who happened to be a world-renowned sommelier, that he quit indignantly. We look back on the WWC and laugh, but then, I think, that fun and enjoyment is the real meaning of <em>oenophilia:</em> the mirth of wine; not feigning the ability to detect notes of wet limestone and saddle soap.</p>

<p><img src="/assets/og/post_smell.jpg" width="20%" /><span class="muted small">Me stopping you mid-sentence to detect notes of petrichor.</span></p>

<h2 id="lets-reel-it-in">Let’s reel it in</h2>

<p>There is an inflection point, though, because Two Buck Chuck really is swill—you know it the moment you smell it. I suspect there’s a minimum threshold where you escape the bottom-shelf nastiness, though the research probably wasn’t testing wines that cheap. But once you’re above that floor<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup> those research findings kick in: people can’t tell the difference and actually prefer the cheaper option. A college girlfriend’s father once shared some wisdom: pick a price range that works for your wallet and figure out what you like within it. Hence the Trader Joe’s boxed Chardonnay sitting proudly in our fridge!</p>

<p>But here’s what I think actually drives my wine enjoyment: variety. When I’ve been drinking California Cab and Pinot<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup> for months, then suddenly have a Sangiovese or Grenache, my palate does a joyful triple lutz. It’s the same with food—you don’t notice your taste buds getting bored until you try something different and they light up again. So when I think I’m enjoying that ‘nicer’ bottle, I suspect it’s often less about superior quality and more about breaking the monotony.</p>

<p>So let the connoisseurs pontificate about terroir whilst you find your Sutton Cellars or secretly enjoy your delicious boxed wine. The science suggests you’re not missing anything—except maybe some pretentious codswallop.</p>

<p>Nunc est bibendum,</p>

<p>— ᴘ. ᴍ. ʙ.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Robbie Gonzalez, <a href="https://gizmodo.com/wine-tasting-is-bullshit-heres-why-496098276">“Wine tasting is bullshit. Here’s why,”</a> <em>Gizmodo</em>, May 8, 2013. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Very high-end restaurants is sort of like this for me, too. I probably burned the roof of my mouth on scalding hot cheese pizza a few too many times to have sophisticated taste, but I’m skeptical. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>I have a price point in mind here, but to geo- and future-proof this post, I’ll refrain from disclosing. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>Cabernet Sauvignon (21% of CA production, 51% of Napa) and Pinot Noir (10% of CA, 22% of Sonoma) dominate Bay Area wine production. California Wine Institute, 2024, wineinstitute.org. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Paul Berens</name></author><category term="culture" /><summary type="html"><![CDATA[Please, don't be *into* wine.]]></summary></entry></feed>