Build Log
Building in the open.
A running record — what shipped, what changed, what's in progress. Short entries, real dates.
May 2026
Phase 1 done. 100 / 100 / 100 on mobile Lighthouse.
Spent today building the structural skeleton for Keel — eight template files, the full layout system, and everything that needs to exist before blocks can live somewhere.
The thing I cared most about: the mobile navigation. Keel's nav wires itself. Three data attributes on the HTML — data-keel-header, data-keel-burger, data-keel-nav — and it works on every page without configuration. No jQuery. No plugin. 60 lines of TypeScript.
The performance result surprised me. First run came back 92. Then I self-hosted the fonts and fixed the color contrast values. Second run: 100 / 100 / 100. FCP 0.9s. TBT 40ms. CLS 0. Compiled CSS is 4.2KB gzipped.
The lesson from the contrast audit: designing with subdued colors is fine, but subdued cannot mean inaccessible. The text3 values I had chosen — used for eyebrows, labels, and footer copy — were sitting at 2.6:1 contrast ratio, well below the WCAG AA minimum of 4.5:1. Fixed across all six color worlds. The adjusted values look almost identical; they're just correct now.
Self-hosting the fonts was the biggest single performance move. Moving from Google Fonts to local WOFF2 files dropped FCP from 1.6s to 0.9s and TBT from 320ms to 40ms. The Google Fonts stylesheet was a 630ms render block. Gone.
Phase 2 starts next: the seven Gutenberg blocks. Each one ships with its schema JSON-LD output. One at a time, each passes Google Rich Results before moving on.
Spent today building the structural skeleton for Keel — eight template files, the full layout system, and everything that needs to exist before blocks can live somewhere.
The thing I cared most about: the mobile navigation. Keel's nav wires itself. Three data attributes on the HTML — data-keel-header, data-keel-burger, data-keel-nav — and it works on every page without configuration. No jQuery. No plugin. 60 lines of TypeScript.
The performance result surprised me. First run came back 92. Then I self-hosted the fonts and fixed the color contrast values. Second run: 100 / 100 / 100. FCP 0.9s. TBT 40ms. CLS 0. Compiled CSS is 4.2KB gzipped.
The lesson from the contrast audit: designing with subdued colors is fine, but subdued cannot mean inaccessible. The text3 values I had chosen — used for eyebrows, labels, and footer copy — were sitting at 2.6:1 contrast ratio, well below the WCAG AA minimum of 4.5:1. Fixed across all six color worlds. The adjusted values look almost identical; they're just correct now.
Self-hosting the fonts was the biggest single performance move. Moving from Google Fonts to local WOFF2 files dropped FCP from 1.6s to 0.9s and TBT from 320ms to 40ms. The Google Fonts stylesheet was a 630ms render block. Gone.
Phase 2 starts next: the seven Gutenberg blocks. Each one ships with its schema JSON-LD output. One at a time, each passes Google Rich Results before moving on.
Started Estuary today. The first product is Keel — a WordPress theme for AI studios, technology agencies, and product companies.
95+ Lighthouse is not a goal you optimise toward at the end. It is a design constraint — one that shapes every decision from the first line of code. If a feature cannot hold the score, the feature changes.
Today: scaffold done. Vite 5 + Tailwind 4 + TypeScript compiling to 6.4KB CSS and 1.6KB JS at first build. Six color worlds defined as CSS custom properties — each named for a specific natural phenomenon precise enough that you could point to it. The HSL accent system is live: one variable controls every accent in the theme simultaneously. Theme is active on staging.
The constraint is set. Now we hold it.
95+ Lighthouse is not a goal you optimise toward at the end. It is a design constraint — one that shapes every decision from the first line of code. If a feature cannot hold the score, the feature changes.
Today: scaffold done. Vite 5 + Tailwind 4 + TypeScript compiling to 6.4KB CSS and 1.6KB JS at first build. Six color worlds defined as CSS custom properties — each named for a specific natural phenomenon precise enough that you could point to it. The HSL accent system is live: one variable controls every accent in the theme simultaneously. Theme is active on staging.
The constraint is set. Now we hold it.
Rebuilt the hero — portrait sits sticky alongside the name through the section. Added a floating compass nav with a Work With Me trigger. Moved the contact modal to the layout so it fires from any page including /now. Copy cleaned throughout.
Migrated PsTally to pstally.com — database moved, DNS cutover with no downtime. Demo lounge, existing sessions, and the nightly seeder cron all intact at the permanent domain. The app is at its address.
Closed the monitoring loop — desktop app, cloud layer, and owner dashboard tested on live hardware. Console state hits the dashboard within seconds. Ghost alerts fire on long unlogged sessions. The system watches without being watched.
Built a Windows desktop app that runs in the background on the lounge PC and feeds console state to the monitoring system automatically. Staff do not manage it — it stays quiet and keeps the system aware.
Launched pstally.com/controller-test — a browser-based diagnostic for PS5, PS4, and Xbox controllers. Every input maps live: trigger pressure, stick range, deadzone drift. Verify a controller works before it reaches a customer.
Hardened the super admin — lounge status controls added alongside impersonation and cross-lounge revenue. Admins can suspend a lounge, step into any owner account, and see aggregated revenue without switching between accounts.
Completed the onboarding wizard — new owners run through 4 steps before the dashboard opens: lounge name, consoles, game types, and staff PINs. A setupComplete flag gates the experience. First impressions are now deliberate.
Built the admin organizer panel — create tournament organizer accounts, view per-organizer stats (tournaments, players, prize totals), and toggle access. The tournament system now has a dedicated admin surface alongside the lounge panel.
Completed the tournament day surface — staff panel cycles through check-in, live bracket scoring, and close. Console cards get a violet tournament state. Owner gets a read-only auto-refreshing bracket view. Full system is live on app.tempforest.com.
Built the organizer panel — create tournament, view live registrations, confirm M-Pesa payments, close registration and generate bracket. Public registration page has a live slot counter and M-Pesa code entry, no redirect on confirm.
Scaffolded the tournament system — 4 DB tables, a bracket engine covering knockout, groups+knockout, and league formats, and a full tRPC router. Tournament organizers get their own auth surface, separate from lounge owners.
Launched the marketing site with landing page, pricing calculator, and public register flow. Added a super admin panel for cross-lounge oversight — lounge creation, revenue aggregation, and owner impersonation.
Seeded a live demo lounge with realistic session data — 7 days of shifts, cashouts, and expenses rebuilt daily at midnight. Any visitor can walk through the full owner experience without signing up.
Added PWA support — installable on Android and iOS from the browser with a custom manifest and icon set. First-time owners now go through a 4-step setup wizard: lounge name, consoles, game types, then staff PINs.
Built the analytics dashboard — revenue trend over 7 and 30 days, peak-hour heatmap, and a breakdown by console, game type, and payment method. Numbers that used to live in mental notes now have charts.
April 2026
Connected the monitoring script to the new Next.js endpoint. Both WordPress and Next.js receive events during migration — lounges move one at a time.
Created Two owners accounts
Scaffolded the rebuild — Next.js 15, TypeScript, PostgreSQL, Drizzle ORM, tRPC. Moved off WordPress. Staff PIN auth and core DB schema in place.
Built the owner dashboard — revenue stats, 7-day trend, console performance, staff leaderboard. AI chat bubble for querying numbers in plain language.
Wired up smart monitoring — PS5 state detection via UDP port 9302, ghost alerts when a console runs too long without a logged session, heartbeat every 5 minutes.
February 2026
Built the staff dashboard — console cards, session logging, shift management, lock screen. Session starts in three taps, shift closes with automatic cash reconciliation.