Work
Two projects in production, and the smaller work underneath them. Both
of the first two are running right now and one of them you can open in a new
tab, which is the only portfolio claim that verifies itself.
Prebuilt Sheds LLC
Marketing site and inventory system for a shed builder.
- Client
- Prebuilt Sheds LLC
- Role
- Sole engineer — build, deploy, and operations
- Period
- 2026 — ongoing
Read the source
Server-rendered Go, SQLite, and htmx: one static binary, four direct
dependencies, no JavaScript toolchain and no CSS framework. Public pages plus a
management area behind auth, CSRF, and a separate body limit.
It runs on a DigitalOcean droplet I administer — nginx and certbot in
front, fail2ban, scheduled and offsite backups — with CI gating format, vet,
tests, a smoke run, and govulncheck before publishing an image to GHCR.
It has a real customer, which is the part that makes it engineering
rather than a demo. Nobody files a bug against a portfolio project.
Decisions worth explaining
-
Pure-Go SQLite, so the binary is actually static
modernc.org/sqlite compiles without cgo, so CGO_ENABLED=0 produces a
static binary and the runtime image needs no libc matching. It trades some raw
speed for a materially simpler build and a smaller image — at this traffic the
speed is irrelevant and the build simplicity is not.
-
Connection pool pinned to one connection
SQLite's PRAGMA foreign_keys is per-connection, and database/sql pools
connections transparently. Without SetMaxOpenConns(1), the ON DELETE CASCADE on
inventory photos would have applied only on whichever connection happened to
have run the pragma — a corruption bug that appears under concurrency and never
in testing. SQLite serializes writes anyway, so the pool was buying nothing.
-
Money is int64 cents, rounded rather than truncated
int64(dollars * 100) silently loses a cent whenever the float
multiplication lands just below the integer. Prices are stored and computed in
cents, and the conversion rounds. Storing money in a float is the bug that shows
up in an invoice six months later and cannot be reconstructed.
-
WAL and a busy timeout, after a real lock race
A SQLite lock race turned CI red intermittently on main. The fix was
enabling WAL and giving every connection — including the smoke script's sqlite3
— a busy timeout. Intermittent CI failures get muted rather than fixed more
often than any other class of bug; this one was reproduced and closed.
-
Every CI job body is a make target
The pipeline holds no commands of its own. When CI and the developer
run different things they drift, and "passes locally, red in CI" becomes
routine — so make ci reproduces the entire workflow on a laptop.
-
The Dockerfile is the source of truth for the Go version
setup-go treats go.mod's go directive as an exact pin rather than the
minimum it is, so a directive of 1.25.0 installed exactly 1.25.0 and govulncheck
reported 26 stdlib findings already fixed in later patches — none of which were
present in the shipped image. CI now parses the Go version out of the Dockerfile
and installs the newest patch of that minor, mirroring how golang:<minor>-alpine
resolves.
- Go
- Echo
- SQLite
- htmx
- Docker
- GitHub Actions
- nginx
- certbot
- DigitalOcean
Illinois Public Defender Statistics
Statewide public-defense data, mapped, for all 102 Illinois counties.
- Client
- Illinois Supreme Court & the Administrative Office of Illinois Courts
- Role
- Sole engineer on the application
- Period
- 2024 — still in production
Visit the live site
Read the source
Commissioned by the Illinois Supreme Court and the Administrative
Office of Illinois Courts, after the Sixth Amendment Center's evaluation of the
state's public defense system found structural deficiencies in oversight and
independence. The application gives public defenders, researchers, and the
courts a way to explore resource distribution across all 102 counties.
The interesting problem was serving one dataset through three different
geographic hierarchies — counties, judicial circuits, and appellate districts —
each needing its own identifiers, colour scales, and legend breaks to drive
choropleth rendering. That shape drove the API: twelve route handlers, organised
by hierarchy rather than by table, returning exactly what the map layer needs
and nothing else. Query time came out of PostgreSQL aggregates, functions, and
indexes rather than out of caching in front of slow queries.
It has been serving continuously since delivery.
Decisions worth explaining
-
The API is shaped by the map, not by the tables
Separate colors, legend, and ids endpoints per hierarchy, plus a
detail route per feature. A generic CRUD surface would have made the client
assemble a choropleth from four round trips and reimplement the classification
breaks in JavaScript; putting the breaks server-side kept the legend and the
fills provably consistent.
-
Query optimisation in Postgres, not in a cache
Aggregates, functions, and indexes brought response times down at the
source. A cache in front of a slow query would have hidden the problem and added
an invalidation bug — and for data that updates rarely and is read constantly,
the index is the correct answer.
-
Performance treated as a measured requirement
First Contentful Paint, Largest Contentful Paint, Total Blocking
Time, and layout shift were tracked as numbers, not impressions. Interactive
maps are heavy by default, and "feels fine on my laptop" is not a finding.
Worth stating plainly. Worth stating plainly: I was the sole code contributor, but not the
whole project. A Northwestern University team collected and analysed the
underlying data, and the court commissioned the work. Worth stating too that
this ran on managed Postgres and App Platform build-on-push — real deployment,
but a platform I consumed rather than administered. Prebuilt is the other half
of that answer, and having shipped both is what makes the comparison worth
having.
- TypeScript
- Next.js
- React
- PostgreSQL
- Tailwind
- DigitalOcean App Platform
On the bench
Small on purpose. They exist because the fastest way to stop
guessing about a layer is to implement it, and because I have never stopped
building things for the sake of building them. Newest first.
-
my_blockchain
C · 2026 · original
A command REPL over a linked-list block store in C, with file-backed persistence and an explicit synced/unsynced state.
-
Unix primitives in C
C · 2024 — 2026 · from spec
ls, tar, and printf implemented against the specification rather than wrapped — argument parsing, flags, and edge cases included.
-
csvq
Go · 2025 · original
A jq-equivalent for CSV: a small query language with its own parser, reading from stdin or a file.
-
go_networking
Go · 2025 · original
Hand-rolled TCP, UDP, TLS, HTTP, and unix-socket servers — the layer most web work sits on top of without looking at.
-
bdg
Go · 2025 · original
A terminal budgeting tool, built because the alternatives all wanted a bank login.
-
my_sqlite
Ruby · 2022 · from spec
A working subset of SQLite — parser, storage, and query execution — written to find out what the file format is actually doing.
Currently open to remote platform, backend, and systems work, or
hybrid in northern Michigan.