← All entries

Wednesday, May 6, 2026

Day 34 — Your logo on every invoice

Barns can now upload their own logo, shown in the sidebar and embedded in every invoice PDF. Plus: the CI archaeology that uncovered a whole class of infrastructure bug I hadn't thought to guard against.

3 min read

Daily entry

Day 34of building EquinePilot

Currently Day 77 · founder build log

Milestones reached

  • barn-logo
  • ci-reliability

Day 34. Two days post-launch. The kind of day that doesn't make the highlight reel but matters enormously — one visible feature that makes the product feel more finished, and several hours of infrastructure work that kept a quiet problem from becoming a loud one.

The feature: barn logos on invoices

Starting today, managers can upload a logo from /manager/settings. It shows up in the sidebar header and — more importantly — gets embedded in every invoice PDF they send to clients.

It sounds small. But invoices are the one document a barn sends every single month that a client actually opens and reads. A branded invoice with the barn's logo signals "we're a real business" in a way that a plain-text document doesn't. For small equestrian barns, many of which are competing with larger facilities that have full staff and polished materials, that signal matters.

There's a detail worth noting in the implementation: the invoice PDF embeds the logo as a base64 data URL rather than fetching it at render time. That means invoice generation is a pure transform — no network calls, no latency on the R2 CDN, no risk that a slow image fetch causes a PDF timeout. The logo is baked in at the point of upload, converted once, and used forever after.

A follow-up commit fixed a subtle bug: large files (above 2 MB) were silently failing to appear in PDFs. The root cause was PDFKit — @react-pdf/renderer's underlying engine — throwing "Unknown version 16717" when fed an image that happened to have metadata pushing it past an internal format boundary. The cap is now 2 MB with a clear error message, and the MIME validation strips content-type parameters before checking (a multipart upload quirk that was causing valid JPEG uploads to fail the type check).

The archaeology: when your migrations table lies to you

This one took most of the afternoon to untangle.

The setup: vercel-dev is our preview database, a copy-on-write clone of production. A CI workflow seeds it with demo data before any PR preview deploy. That workflow was failing — not spectacularly, just quietly producing CredentialsSignin errors when anyone tried to log into the preview app.

Tracing back: db:seed was failing with column "archived_at" does not exist. Drizzle's __drizzle_migrations table said migration 0018 was applied. The actual barns table said it wasn't.

The __drizzle_migrations table records what ran, not what succeeded. If a migration partially applied — or if the Neon copy-on-write snapshot was taken at an unlucky moment — the tracker and reality diverge. And Drizzle's db:migrate is a no-op when the journal says you're current.

Three fixes shipped to address this:

  1. db:provision now delegates to the library code path, which wraps every migration in IF NOT EXISTS guards. The script path was running raw SQL files and hard-failing on re-runs. One code path now, same behavior everywhere.

  2. The seed-preview workflow re-gained its migrate:all + provision steps — now safe to run since provision is idempotent.

  3. A new daily CI job compares Drizzle's expected schema against information_schema.columns for every schema in the database — public plus every tenant — and exits non-zero on any column drift. It runs at 09:00 UTC and is also triggerable manually. The goal is to catch this class of bug before it turns into a production incident.

On the cadence

Day 34. Launch was two days ago. The last 48 hours have been exactly what the first week post-launch should be: close the obvious quality gaps, plug the compliance holes (the refunds/cancellation page went up today — required by Stripe's click-to-cancel rules), and get the infrastructure into a state where the next feature can ship smoothly.

The barn logo is the kind of polish that existing customers notice and appreciate. The CI archaeology is the kind of work that means the feature after next doesn't get delayed by a broken preview environment.

Next up: CourseWalk v1 implementation continues. The database schema landed two days ago; the auth layer and first mobile API routes are next.