Programming
-
I built a hacker terminal typing game. Type Python symbols to “decrypt” corrupted code, hex becomes assembly, then pseudocode, then real source. Mess up and the screen glitches. A typing tutor for programmers that doesn’t feel like one.
-
What I Actually Mean When I Say "Vibe Coding"
Have you thought about what you actually mean when you tell someone you vibe code? The term gets thrown around a lot, but I think there’s a meaningful distinction worth spelling out. Here’s how I think about it.
Vibe coding, to me, doesn’t mean you’re using an LLM because at this point, it’s hard to avoid using an LLM no matter what type of work you’re doing. The distinction isn’t about the tools, it’s about the intent and the stakes.
Vibe Coding vs. Engineering Work
To me, vibe coding is when I have a simple idea and I want to build a self-contained app that expresses that idea. There’s no spec, no requirements doc, no code review. Just an idea and the energy to make it real.
Engineering work, whether that’s context engineering, spec-driven development, or the old-fashioned way; is when I’m working on open source or paid work. There’s structure, there are standards, and the code needs to hold up over time.
Both are fun. But I take vibe coding less seriously than engineering work.
Vibe coding is the creative playground. Engineering is the craft.
My Setup
All my vibe coded apps live in a single repository. The code isn’t public, but the results are. You can find all my ideas at logan.center.
I have it set up so that every time I add a new folder, it automatically gets its own subdomain.
I’m using Caddy for routing and deploying to Railway. I have an idea, create the app, and boom — it’s live.
I would like to open source a template version of this setup so other people could deploy to something like Railway easily, but I haven’t gotten around to building that yet. One day.
For my repository, I decided it would be fun to build everything in Svelte instead of React.
That’s why you may have seen a bunch of posts from me lately about learning how Svelte works. It’s been fun because the framework stays out of your way and lets you move fast, which is exactly what you want when you’re chasing the vibe.
So, for me, vibe coding is a specific thing: low stakes, high creativity, self-contained apps, and the freedom to just build without overthinking it.
I mean I’m not crazy, I still have testing setup…
-
Stop Using pip. Seriously.
If you’re writing Python in 2026, I need you to pretend that pip doesn’t exist. Use Poetry or uv instead.
Hopefully you’ve read my previous post on why testing matters. If you haven’t, go read that first. Back? Hopefully you are convinced.
If you’re writing Python, you should be writing tests, and you can’t do that properly with pip. It’s an unfortunate but true state of Python right now.
In order to write tests, you need dependencies, which is how we get to the root of the issue.
The Lock File Problem
The closest thing pip has to a lock file is
pip freeze > requirements.txt. But it just doesn’t cut the mustard. It’s just a flat list of pinned versions.A proper lock file captures the resolution graph, the full picture of how your dependencies relate to each other. It distinguishes between direct dependencies (the packages you asked for) and transitive dependencies (the packages they pulled in). A
requirements.txtdoesn’t do any of that.Ok, so? You might be asking yourself.
It means that you can’t guarantee that running
pip install -r requirements.txtsix months or six minutes from now will give you the same copy of all your dependencies.It’s not repeatable. It’s not deterministic. It’s not reliable.
The one constant in Code is that it changes. Without a lock file, you’re rolling the dice every time.
Everyone Else Figured This Out
Every other modern language ecosystem “solved” this problem years ago:
- JavaScript has
package-lock.json(npm) andpnpm-lock.yaml(pnpm) - Rust has
Cargo.lock - Go has
go.sum - Ruby has
Gemfile.lock - PHP has
composer.lock
Python’s built-in package manager just… doesn’t have this.
That’s a real problem when you’re trying to build reproducible environments, run tests in CI, or deploy with any confidence that what you tested locally is what’s running in production.
What to Use Instead
Both Poetry and uv solve the lock file problem and give you reproducible environments. They’re more alike than different — here’s what they share:
- Lock files with full dependency resolution graphs
- Separation of dev and production dependencies
- Virtual environment management
pyproject.tomlas the single config file- Package building and publishing to PyPI
Poetry is the more established option. It’s at version 2.3 (released January 2026), supports Python 3.10–3.14, and has been the go-to alternative to pip for years. It’s stable, well-documented, and has a large ecosystem of plugins.
uv is the newer option from Astral (the team behind Ruff). It’s written in Rust and is 10–100x faster than pip at dependency resolution. It can also manage Python versions directly, similar to mise or pyenv. It’s currently at version 0.10, so it hasn’t hit 1.0 yet, but gaining adoption fast.
You can’t go wrong with either. Pick one, use it, and stop using pip.
- JavaScript has
-
Why Testing Matters
There is a fundamental misunderstanding about testing in software development. The dirty and not-so-secret, secret, in software development is TESTING is more often than not seen as something that we do after the fact, despite the best efforts from the TDD and BDD crowd.
So why is that the case and why does TESTING matter?
All questions about software decisions lead to a maintainability answer.
If you write software that is intended to be used, and I don’t care how you write it, what language, what framework or what your background is; it should be tested or it should be deleted/archived.
That sounds harsh but it’s the truth.
If you intended to run the software beyond the moment you built it, then it needs to be maintained. It could be used by someone else, or even by you at a later date, it doesn’t matter. Test it.
if software.intended_to_run > once: testing = requiredThat’s just the reality of the craft. Here is why.
Testing Is Showing Your Work
Remember proofs in math class? Testing is the software equivalent. It’s how you show your work. It’s how you demonstrate that the thing you built actually does what you say it does, and will keep doing it tomorrow.
Chances are your project has dependencies. What happens to those dependencies a month from now? Five years from now? A decade?
Code gets updated. Libraries evolve. APIs change. Testing makes sure that those future dependency updates aren’t going to cause regression issues in your application.
It’s a bet against future problems. If I write tests now, I reduce the time I spend debugging later. That’s not idealism, it’s just math.
T = Σ(B · D) - CWhere B = probability of bug, D = debug time, C = cost of writing tests and T is time saved.
Protecting Your Team’s Work
If you’re working on a team at the ole’ day job, you want to make sure that the code other people are adding isn’t breaking the stuff you’re working on or the stuff you worked on six months ago, add tests.
Tests give you that safety net. They’re the contract that says “this thing works, and if someone changes it in a way that breaks it, we’ll know immediately.”
Without tests, you’re essentially hoping for the best and hope isn’t good bet when it comes to the future of a software based business.
Your Customers Are Not Your QA Team
Auto-deploying to production without any testing or verification process? That’s just crazy. You shouldn’t be implicitly or explicitly asking your customers to test your software. It’s not their responsibility. It’s yours.
Your job is to produce software that’s as bug-free as possible. Software that people can rely on. Reliable, maintainable software, that’s what you owe the people using what you build.
Bringing Testing to the Table
Look, I get it. Writing tests isn’t the most fun part of the job. However, a lot has changed in the past couple of years. You might have heard about this whole AI thing? With the Agents we all have available to us, we can add tests with as little as 5 words.
“Write tests on new code.”
Looking back at that forumla for C, we can now see that the cost of writing tests is quickly approaching zero. It just takes a bit of time for the tests to be written, it just takes a bit of time to verify the tests the Agent added are useful.
Don’t worry about doing everything at the start and setup a full CI pipeline to run the tests. Just start with the 5 words and add the complicated bits later.
No excuses, just testing.
-
Laravel - The PHP Framework For Web Artisans
Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing you to create without sweating the small things.
-
Why Svelte 5 Wants `let` Instead of `const` (And Why Your Linter Is Confused)
If you’ve been working with Svelte 5 and a linter like Biome, you might have run into this sitauation:
use
constinstead ofletHowever, Svelte actually needs that
let.Have you ever wondered why this is?
Here is an attempt to explain it to you.
Svelte 5’s reactivity model is different from the rest of the TypeScript world, and some of our tooling hasn’t quite caught up yet.
The
constRule Everyone KnowsIn standard TypeScript and React, the
prefer-construle is a solid best practice.If you declare a variable and never reassign it, use
const. It communicates intent clearly: this binding won’t change. You would thinkBut there is confusion when it comes to objects that are defined as
const.Let’s take a look at a React example:
// React — const makes perfect sense here const [count, setCount] = useState(0); const handleClick = () => setCount(count + 1);count is a number (primitive). setCount is a function.
Neither can modify itself so it makes sense to use
const.Svelte 5 Plays by Different Rules
Svelte 5 introduced runes — reactive primitives like
$state(),$derived(), and$props()that bring fine-grained reactivity directly into JavaScript.Svelte compiler transforms these declarations into getters and setters behind the scenes.
The value does get reassigned, even if your code looks like a simple variable.
<script lang="ts"> let count = $state(0); </script> <button onclick={() => count++}> Clicked {count} times </button>Biome Gets This Wrong
But they are trying to make it right. Biome added experimental Svelte support in v2.3.0, but it has a significant limitation: it only analyzes the
<script>block in isolation.It doesn’t see what happens in the template. So when Biome looks at this:
<script lang="ts"> let isOpen = $state(false); </script> <button onclick={() => isOpen = !isOpen}> Toggle </button>It only sees
let isOpen = $state(false)and thinks: “this variable is never reassigned, useconst.”It completely misses the
isOpen = !isOpenhappening in the template markup.If you run
biome check --write, it will automatically changelettoconstand break your app.The Biome team has acknowledged this as an explicit limitation of their partial Svelte support.
For now, the workaround is to either disable the
useConstrule for Svelte files or addbiome-ignorecomments where needed.What About ESLint?
The Svelte ESLint plugin community has proposed two new rules to handle this properly:
svelte/rune-prefer-let(which recommendsletfor rune declarations) and a Svelte-awaresvelte/prefer-constthat understands reactive declarations. These would give you proper linting without the false positives.My Take
Svelte 5’s runes are special and deserve their own way of handling variable declarations.
React hooks are different.
Svelte’s compiler rewrites your variable.
I like Svelte.
Please fix Biome.
Don’t make me use eslint.
-
REPL-Driven Development Is Back (Thanks to AI)
So you’ve heard of TDD. Maybe BDD. But have you heard of RDD?
REPL-driven development. I think most programmers these days don’t work this way. The closest equivalent most people are familiar with is something like Python notebooks—Jupyter or Colab.
But RDD is actually pretty old. Back in the 70s and 80s, Lisp and Smalltalk were basically built around the REPL. You’d write code, run it immediately, see the result, and iterate. The feedback loop was instant.
Then the modern era of software happened. We moved to a file-based workflow, probably stemming from Unix, C, and Java. You write source code in files. There’s often a compilation step. You run the whole thing.
The feedback loop got slower, more disconnected. Some languages we use today like Python, Ruby, JavaScript, PHP include a REPL, but that’s not usually how we develop. We write files, run tests, refresh browsers.
Here’s what’s interesting: AI coding assistants are making these interactive loops relevant again.
The new RDD is natural language as a REPL.
Think about it. The traditional REPL loop was:
- Type code
- System evaluates it
- See the result
- Iterate
The AI-assisted loop is almost identical:
- Type (or speak) your intent in natural language
- AI interprets and generates code
- AI runs it and shows you the result
- Iterate
You describe what you want. The AI writes the code. It executes. You see what happened. If it’s not right, you clarify, and the loop continues.
This feels fundamentally different from the file-based workflow most of us grew up with. You’re not thinking about which file to open, You’re thinking about what you want to happen, and you’re having a conversation until it does.
Of course, this isn’t a perfect analogy. With a traditional REPL, you have more control. You understood exactly what was being evaluated because you wrote it.
>>> while True: ... history.repeat() -
I kinda want to build more git/hub tools. I guess I have been trying to get to 20 repos that I am proud of. I certainly don’t all of them public. I am taking most of mine private or archiving. I should probably focus on like 5 or 1. Open claw happens when you become hyper fixated on just one thing.
-
Why Data Modeling Matters When Building with AI
If you’ve started building software recently, especially if you’re leaning heavily on AI tools to help you code—here’s something that might not be obvious: data modeling matters more now than ever.
AI is remarkably good at getting the local stuff right. Functions work. Logic flows. Tests pass. But when it comes to understanding the global architecture of your application? That’s where things get shaky.
Without a clear data model guiding the process, you’re essentially letting the AI do whatever it thinks is best. And what the AI thinks is best isn’t always what’s best for your codebase six months from now.
The Flag Problem
When you don’t nail down your data structure upfront, AI tools tend to reach for flags to represent state. You end up with columns like
is_draft,is_published,is_deleted, all stored as separate boolean fields.This seems fine at first. But add a few more flags, and suddenly you’ve got rows where
is_draft = trueANDis_published = trueANDis_deleted = true.That’s an impossible state. Your code can’t handle it because it shouldn’t exist.
Instead of multiple flags, use an enum:
status: draft | published | deleted. One field. Clear states. No contradictions.This is just one example of why data modeling early can save you from drowning in technical debt later.
Representation, Storage, and Retrieval
If data modeling is about the shape of your data, data structures determine how efficiently you represent, store, and retrieve it.
This matters because once you’ve got a lot of data, migrating from one structure to another, or switching database engines—becomes genuinely painful.
When you’re designing a system, think about its lifetime.
- How much data will you store monthly? Yearly?
- How often do you need to retrieve it?
- Does recent data need to be prioritized over historical data?
- Will you use caches or queues for intermediate storage?
Where AI Takes Shortcuts
AI agents inherit our bad habits. Lists and arrays are everywhere in their training data, so they default to using them even when a set, hash map, or dictionary would perform dramatically better.
In TypeScript, I see another pattern constantly: when the AI hits type errors, it makes everything optional.
Problem solved, right? Except now your code is riddled with null checks and edge cases that shouldn’t exist.
Then there’s the object-oriented problems. When building software that should use proper OOP patterns, AI often takes shortcuts in how it represents data. Those shortcuts feel fine in the moment but create maintenance nightmares down the road.
The Prop Drilling Epidemic
LLM providers have optimized their agents to be nimble, managing context windows so they can stay productive. That’s a good thing. But that nimbleness means the agents don’t always understand the full structure of your code.
In TypeScript projects, this leads to prop drilling: passing the entire global application object down through nested components.
Everything becomes tightly coupled. When you need to change the structure of an object, it’s like dropping a pebble in a pond. The ripples spread everywhere.
You change one thing, and suddenly you’re fixing a hundred other places that all expected the old structure.
The Takeaway
If you’re building with AI, invest time in data modeling before you start coding. Define your data structures. Think about how your data will grow and how you’ll access it.
The AI can help you build fast. But you still need to provide the architectural vision. That’s not something you can blindly trust the AI to handle, not yet, anyway.
-
A specification for adding human and machine readable meaning to commit messages