Software
Independent products, community websites, and experiments.
Sitzmark
Study words, follow player progress, and explore Scrabble tournament results.
I build the React/Vite interface with TypeScript, Python services, and PostgreSQL reporting. Production data covers 21,392 players, 8,915 tournament events, 1,353,388 distinct games, and 2,662,989 player-round result rows.
Implementation details
- Entity resolution across Scrabble organizations combines source provenance and human review of ambiguous matches.
- PostgreSQL materialized views cut a player-dashboard request from about 10 seconds to 0.26 seconds.
- Repaired migration regressions that erased resolved records and added regression and integrity checks.
- Maintain 1,008 passing tests: 560 Python backend, 404 Vitest, and 44 Playwright. Led a root-cause cleanup of 159 failing tests without skipping any.
- GitLab CI configured; DigitalOcean hosting uses systemd and scripted deployment. Database migrations, structured logging, and backup/restore documentation support operations.
- Player filters, historical ratings, accounts, preferences, admin corrections, source reconciliation, content aggregation, and experimental word-game interfaces.
- Prototyped an OCR pipeline that combines a board photograph and move list into a GCG game record with move-by-move playback; not released as a general-purpose feature.
- Codex and Claude support a spec-driven, test-gated, human-reviewed workflow.
React/Vite / TypeScript / Python / PostgreSQL / Vitest / Playwright
Visit project ↗Builds on Badqoph, a Scrabble community directory begun in 2007.
Builds on earlier ScrabTube work.
Lex or Hex needs no account. Word Wisp requires an account.
Scrabble Explorer
Search, replay, and ask plain-English questions about 60,728 recorded Scrabble games.
Downloads 62,323 annotated game files politely (one request a second, resumable) and replays them with a parser shared with Sitzmark: 1.7 million moves, 3.1 million word occurrences, and 6.0 million tile placements in SQLite.
Implementation details
- “Ask the archive”: an open-weight model (llama.cpp) running on my own hardware, reached through a reverse SSH tunnel, turns each question into one read-only SQL query. Answers come back as tables or charts that link to the games, players, and words they mention, and the SQL is always shown.
- Safety: the model never touches the database. Generated SQL runs in a separate process on a read-only connection; SQLite’s authorizer checks every query against an allowlist of tables, columns, and functions; every query is limited to 500 rows, 2 MB, and 15 seconds; result rows are never sent to the model; write requests, attempts to get secrets, and prompt-override attempts are refused before any model call.
- Evaluation: 53 reference questions scored by exact row-for-row comparison of results (no model grading) at temperature 0: 47 of 53 (88.7%) overall, 10 of 12 on held-out questions written after tuning ended, and 19 of 19 correct refusals and clarifying questions. These measurements use a fixed snapshot of the archive.
- Performance: a score-range question took 45 seconds because SQLite’s query planner misjudged a filter; a one-line hint brought the query to about 0.3 seconds.
- Hosting: Astro and Svelte on a shared 1 GB droplet under a 400 MB memory cap, with Apache in front. When the home model is offline, the site says so and everything else keeps working.
TypeScript / Astro / Svelte / Node.js / SQLite (FTS5) / Drizzle ORM / Vitest / Playwright / llama.cpp
Visit project ↗Podcast Dynamite
A podcast catalog connecting networks, shows, episodes, and credits.
A podcast catalog built from scratch with RSS ingestion and de-duplication. Earlier implementations used React/PHP/MySQL, Perl ingestion, and cron. The current rebuild combines Angular with RxJS, Java Spring Boot, jOOQ, PostgreSQL, Docker builds, and CI configuration. A collaborator wrote backend authentication.
Angular / TypeScript / RxJS / Java Spring Boot / jOOQ / PostgreSQL / Docker
Visit project ↗
Wonderfish Films website
The website for my separate filmmaking and editing portfolio.
A Next.js 14/TypeScript site using App Router, server and client components, responsive images, metadata, and same-origin contact/CAPTCHA Route Handlers that proxy to the shared backend. Scripted Linux releases use checksummed artifacts, SSH transfer, and smoke checks; GitLab CI is configured. Film credits and editing samples live on the separate Film/Media page and Wonderfish.
Next.js 14 / React / TypeScript
Visit project ↗
Charlotte Scrabble Club
Club news, study resources, and an archive of tournament photographs.
A React and TypeScript site with resource pages, a photo archive, responsive navigation, and contact forms backed by the shared contact service.
React / TypeScript / Vite
Visit project ↗
Anagrams on Stream
Anagrams played through Twitch chat, with a desktop display for streamers.
Go handles the game and chat connection; JavaScript renders the tile pool and players in an NW.js desktop interface.
Go / JavaScript / NW.js / Twitch IRC
View source ↗
Scrabble Rotisserie
A fantasy-style tournament game run for about ten championship cycles.
Perl-based ingestion and scheduled roster and standings updates, PHP interfaces, MySQL persistence, and JavaScript. Reconciled irregular player names, changing divisions, withdrawals, and official tournament records.
Perl / PHP / MySQL / JavaScript
View source ↗Zephyr
Queries a word-study database and generates HTML statistics.
A Perl/DBI command-line tool that queries SQLite and generates HTML word-study statistics.
Perl / DBI / SQLite / HTML







