Software Engineer — Shelter Island, NY

Barnabas
Kendall

I design and build web applications, then write about the process — and about whatever else catches my attention.

> currently rebuilding this site_
Barnabas Kendall

The Blog

page 1 of 3
AIIdeas

Is It Cheating?

There’s a lot of hand-wringing on the Internet about engineers using LLMs to code: whether we’re doing ourselves a disservice, letting our skills atrophy, getting dumber, shooting ourselves in the foot, digging our own graves, etc. My own experience working alongside AI tools like Claude Code has reignited my imposter syndrome to a degree.

I’ve felt this way before. Years ago I was asked to interpret a half-hour speech from English into Japanese. I was given the outline the speaker would use ahead of time, and I knew my Japanese was just barely good enough to do an adequate job with preparation and my own notes on the tricky vocabulary. But I wanted better for the audience, so I “cheated.”

I got lucky: the speaker gave the same speech in two other cities in the prior weeks, each time interpreted live by a different native Japanese speaker. I attended both and made recordings. My heart sank as I listened. Their fluency and speed were on another level, and I realized I was in way over my head. The speaker kept wandering off his outline: jokes, metaphors, asides, and both interpreters rolled with it effortlessly. I transcribed their work and pored over it, looking for hints. But the closer I looked, the more the cracks showed. For all their polish, neither was perfect. One would land a more accurate or evocative phrase where the other fumbled. Sometimes they’d quietly ask the speaker to repeat or rephrase a line; other times they’d quietly drop a minor idea, or reach for a complicated phrase when a simple one would have done.

Interpreting from English

Armed with their prior performances, I ended up doing a much better job than I would have on my own, so much better that I felt a little embarrassed. Afterward, when people congratulated me on my sudden interpreting talent, I admitted to “cheating” and gave credit to the other two interpreters. My bilingual friend Yuji shrugged this off as simply good preparation. He pointed out I’d also had an advantage neither interpreter had: as a native English speaker, I had 100% comprehension of the source material. During that talk he himself was thrown by a mangled English idiom, mishearing “two peas in a pod” as something like “two bees in a pod,” and losing a sentence chasing the wrong image.

So was I cheating? If the point was to test my personal, unaided ability to interpret English into Japanese, then yes. Or if I’d basked in the praise and let people believe I was naturally that gifted, also yes. But the actual point was never me. The focus was on the speaker and what he had to say, not on my performance. The goal was audience understanding, not Barnabas glory. The ends justified the means, because the means were never really the point.

Writing software with AI coding agents, I feel a similar twinge of guilt. I labor over a prompt, turn a state-of-the-art agent loose on a giant codebase, and it comes back with sharp analysis and working code in a fraction of the time it would have taken me. It makes me feel old and slow. But on closer inspection, the polish has cracks too: my counterpart sometimes misses the “metaphor” (the right abstraction), over-engineers something that should be simple, or quietly drops a requirement altogether. Nobody seriously expects engineers to hand-type all their code anymore, so while my raw “output” stopped being a measure of my ability, that was never really the point either. The real question is whether I, with all my tools and preparation and experience, can still produce tasteful craftsmanship. That has always been the only task worth doing.

Writing

Just One Bite

A short parable about trust, taste, and the difference between a job and a craft.

There was once a small ship that sailed a steady, well-known coast. Aboard her were a captain, a first mate, and a cook.

The captain was a careful man. Each evening he sent for his plate and studied it like a chart: not enough salt here, too much butter there, cut the potatoes smaller next time. He had eaten the same handful of dishes for ten years and saw no reason a hundred more wouldn’t do just as well.

The first mate carried these instructions below deck, word for word, and brought them back the same way. He had served under the captain a long time and took quiet pride in knowing the man’s tastes before the man had to state them.

The cook did his best with what he was handed. He’d spent thirty years in kitchens before this ship, and most nights he could tell the dish being requested wasn’t really what the captain wanted, only what the captain knew how to ask for. But a cook cooks, so he cooked.

One evening he made something of his own, using spices from his personal collection. Nothing strange — no dish the captain couldn’t recognize the parts of, just put together with more care than usual, and a taste he was certain the man would like, if only he tried it.

The cook tries

He brought it to the first mate. “Take this up to him. Just one bite. He doesn’t have to keep it.”

The mate shook his head. “I know him. He doesn’t like surprises on his plate, and he likes even less finding out money is being wasted on ingredients he won’t eat. If he hates it, that’s on me.”

“He might love it.”

“He might. But I’m not going to be the one who finds out.” The mate paused, then added, “Also, I tasted it myself, in the galley. I’d have gone lighter on the thyme.”

“Then come taste it with him,” the cook said. “Let him decide, with both of us in the room.”

There was no room to be had, the mate said. Not this season.

So the dish was thrown overboard uneaten, and the cook returned to smaller potatoes and less seasoning. What stayed with him wasn’t the refusal — men refuse things — but the shrug underneath it, the sense that no one aboard found it strange for a man to eat the same bland dishes for ten years running and call it a decision rather than a habit he’d never once questioned. It was his ship, his plate, his right. The cook didn’t dispute that. He just found it a small and joyless way to eat.

At the next port, the cook came up from below and said he’d be leaving.

They were baffled. By their measure he’d done the job well — the plates came out, the crew ate, no one had gone hungry. “We don’t understand,” the mate said. “You’re good at this.”

“I know,” the cook said. “But you don’t want a cook. You want someone to combine what’s already been chosen, the way it’s always been combined. That’s honest work, and someone should do it. I just spent thirty years learning to be a chef — and a chef needs a kitchen where the only person who has to be talked into a new dish is the one eating it.”

He picked up his knives and went to go find a restaurant of his own.

The cook leaves

The Grug Brained Developer is a plea for humility over cleverness, dressed up as a caveman’s diary.

kottke.org is Jason Kottke’s long-running “liberal arts” blog, and it’s the direct inspiration for these short link posts — full posts below are still mine, but the little in-between notes are a Kottke move.

MapLibre GL JS is the open-source fork that kept vector-tile mapping alive after Mapbox GL JS went proprietary — worth a look if you, like me, have spent time elbow-deep in PostGIS.

VueSide Project

Cheaty Flashcards

My niece just finished the fourth grade and we were commiserating about learning multiplication tables. We thought, what if there was a game for learning math that let you cheat just a little bit so you don’t get too discouraged? Soon after, Cheaty Flashcards was born.

Cheaty Flashcards gameplay

Some of the tech choices I made are as follows. I reached for my favorite Vue + Vite combo and used the TypeScript starter. I styled it with Tailwind CSS and daisyUI, a great combination of sensible boilerplate and flexibility. Other dependencies include Unhead for dynamic page titles, Fontsource for the web font Fredoka, and unplugin-icons to embed Feather SVGs as components. The new-to-me library this time was howler.js for playing sound effects.

I pushed the app source code to a private GitHub repository. I’m using Cloudflare Pages as the host and tracking usage with Beam Analytics. The whole thing took only a few hours between idea and v1 on the web.

Some may see the above and bemoan the state of frontend development. “What ever happened to FTPing some static files to a shared web server?” they cry, yelling at the cloud. I’m tempted to agree, especially for a little free toy app. But, tooling like this brings the ability to build complexity and make changes rapidly. And personal projects like these are to me what practicing scales are to a musician.

Astro

Starlight

This month I had the opportunity to take Starlight for test drive as a demo for my team’s internal documentation. Although Starlight currently comes with a warning that it’s in early development, we were impressed by its polish and features. I think it has real potential to challenge established documentation SSGs like VitePress or Docusaurus.

If you already have Markdown files, it is absurdly easy to publish them with Starlight. Those are basic table stakes for any SSG, though. If you need to do something unusual in your docs site, such as host an API or enforce authentication, good news! Starlight is built on Astro, so you have the full power of Astro and its integrations at your disposal. For our demo, I enabled diagrams with the astro-diagram plugin. Like any Astro site, you can create components in Astro’s syntax or any supported framework including React, Preact, Svelte, Vue, SolidJS, AlpineJS and Lit.

Documentation is not worth much if the content is not discoverable. You can configure the sidebar manually or just point a sidebar section at a directory, which is nice. But Starlight’s pièce de résistance IMO is the excellent static site search courtesy of Pagefind, and I’m already a fan. Other solutions in this space don’t have a built-in search, have a very minimal search UI, or rely on an external service like Algolia.

The drawback of Starlight, aside from its wet-behind-the-ears newness, is the inflexibility. There’s no way to make a custom page that uses the Starlight’s layout, as of today. You can only have one sidebar, so this might make certain kinds of documentation difficult or impossible. VitePress supports multiple sidebars if that’s a deal breaker. It is certainly difficult to balance simplicity with customizability.

For our use case, we ultimately decided against using Starlight in favor of GitHub’s wiki feature. It might look comparatively boring and have a terrible search UX, but GitHub’s wiki edit and publish workflow is good enough for internal documentation. Besides, access is already restricted to the team members, something we’d have to replicate somehow with Starlight.

MiniSearch is the small, fast full-text search library quietly powering this site’s search.

NuxtVue

Trying Out Nuxt

I have enjoyed using Astro to run both this site and DBaaS Review. I’ve written before about Astro now and again. Recently I decided to migrate DBaaS Review from Astro on Render to Nuxt on Cloudflare.

My original motivation was receiving a bill from Render for DBaaS Review’s API. Now $7/month is pretty reasonable, but it got me to thinking. If I could switch from Render to Cloudflare Pages, I might be able to stay within the free tier for a while. Plus, having two different projects, one for the API and another for the UI, was getting tiresome. How good would it be to have just one static frontend with a dynamic API, all hosted on the edge for free?

As long as I was going to tear everything apart, I thought “why not try Nuxt?” After all, Nuxt/Nitro has a Cloudflare Pages preset. So I merged the Express API with the Astro site to form a single Nuxt app. It went fairly well at first, and Nuxt has a slick DX. I especially appreciated the file-based-routing for the API endpoints. It was tedious converting the JSX-like .astro files to .vue components, but I prefer the Vue syntax anyway, especially for loops:

Astro

---
const visible = true;
const items = ["Dog", "Cat", "Platypus"];
---
{visible && <p>Show me!</p>}
<ul>
  {items.map((item) => (
    <li>{item}</li>
  ))}
</ul>

Vue

<script setup>
import { ref } from "vue";
const visible = ref(true);
const items = ref(["Dog", "Cat", "Platypus"]);
</script>
<template>
  <p v-if="visible">Show me!</p>
  <ul>
    <li v-for="item in items">{{ item }}</li>
  </ul>
</template>

I ended up getting stuck on some sharp edges with the Nuxt/Cloudflare migration. One was that I just couldn’t find a nice way for Nuxt to find and open a SQLite file. I finally realized that Cloudflare Pages’ workers would never allow me to access the filesystem and load a SQLite file anyway.

Discouraged, I tried implementing a combined app with Astro in Docker. It went very smoothly. I was able to reuse much of the work I did for the Nuxt API endpoints, and reading the SQLite file worked without a fuss. Astro’s Docker SSR recipe worked well. Now that I had a containerized app, I was able to deploy my app to Fly.io. Render and Fly both have a free tier which they spin down services for inactivity, but unlike Render, Fly.io’s spin up seems speedy enough that it doesn’t feel like a penalty. Fly Machines are Firecracker VMs that boot in about 300ms. Goodbye, Render.

Could a containerized Nuxt have worked on Fly.io? Likely yes. Could SSR Astro have worked on Cloudflare? Maybe? I admit to my bias and laziness; Nuxt had to be much better than Astro to be worth the migration hassle, but it just wasn’t. They’re both about equal in terms of Pros and Cons, except I’d give the edge to Astro’s docs right now. I’m glad I tried Nuxt and I think for a brand new app I’d try it again. But then again, I might experiment with SvelteKit or SolidStart instead. Anything but Next.js/React. 🤢 PHP even.

As for the main thing I don’t like about Astro, the JSX style templates? Using SSR Vue components within Astro is feasible to a point. But, there are certain things that can only happen inside an .astro file, so I just have to learn to live with weird loops and conditionals.

Analytics

Beam Analytics

This site is hosted on Cloudflare Pages, and so I have some visibility to the traffic. Still, it’s useful to have more details than the Cloudflare analytics provide. Years ago the default option would have been Google Analytics, but there are more privacy-friendly alternatives available now.

Until today I was using Plausible Analytics on this site and my side project. It costs $9.75/month which felt pretty reasonable at the time. I am nowhere near the 10k page views that would force me to upgrade my plan, though. When calculated as price per page view even now, it’s… a lot. That’s especially in light of everything else here (except for the domain name) being free.

The other day however, I saw an HN post about Beam Analytics (affiliate link). The free tier is 100k page views per month, and that seems like a nice value. I will try it out and probably cancel my Plausible plan.