This is a submission for Weekend Challenge: Passion Edition
What I Built
I built Buzzword Bingo, a multiplayer bingo game for conferences, webinars and meetings where players mark off the inevitable buzzwords as they appear.
The application allows someone to create a game, share a link with participants, and let everyone play along on their own unique bingo board. The first player to complete a row, column or diagonal wins.
Under the hood, though, the game itself was almost secondary.
The real goal was to answer a question I had been wondering about for a while:
How far can I push Claude with specification-driven development while still achieving reliable type coverage and maintaining the coding standards I expect from a production Python project?
The project became an experiment in AI-assisted software engineering, strict typing, and how much guidance modern coding agents actually need to produce maintainable software.
Demo
There is no live demo, but you can have a look at the screenshots taken by playwright during testing
Code
Repository:
How I Built It
Specification Driven Development
The project followed a specification-driven approach using Speckit.
Rather than iterating directly in code, I created specifications describing what the system should do and allowed Claude to implement them.
A big accelerator for the project was using scaf for the initial bootstrap. Rather than spending the first few hours wiring together repository structure, CI, containerization, infrastructure, and developer tooling, I started from a production-oriented foundation and focused on shaping it to match my own preferences. Having Kubernetes manifests, Terraform, deployment pipelines, and modern Python tooling available from day one made it much easier to concentrate on the actual experiment: how far specification-driven development and AI coding agents could take the application.
I ended up needing three major specifications:
- Project scaffolding
- Starting from a project generated with
scaf. - Refining the generated structure to match my personal preferences.
- Adding all the infrastructure and tooling I typically expect in a modern project.
- Backend implementation
- Django models and business logic.
- Server-rendered templates.
- HTMX interactions.
- Capability URL based authorization.
- Frontend implementation
- Visual styling and user experience.
- Responsive layouts.
- End-to-end testing using Playwright.
Django Without the JavaScript Framework
The application uses:
- Django
- HTMX
- Django templates
- PostgreSQL
HTMX turned out to be an excellent fit for this type of application.
Most interactions consist of:
- clicking a square,
- sending a POST request,
- returning an updated HTML fragment,
- swapping it into the page.
No client-side state management was required.
Capability URLs
One design decision I particularly liked was using capability URLs instead of authentication.
Each board receives a unique UUID:
/board/5b97b663-1f2f-4e54-8d2f-f45f3272f870/
Possession of the URL grants access to that board.
This removes the need for:
- user accounts,
- sessions,
- authentication,
- authorization logic.
For a lightweight conference game this felt like the right trade-off.
Going All-In On Type Safety
I care a lot about clean code and strong typing in Python, so I decided to push the type system as far as possible.
Instead of relying on a single type checker, I combined:
This was paired with a strict ruff configuration with almost every rule enabled.
One of the goals of the experiment was to see whether Claude could operate effectively within these constraints.
What Worked
This instruction worked well:
Prefer precise, narrow types (
Enum,NewType,TypedDict, dataclasses withFinalorLiteralfields) overAny, untypeddictorlist, or stringly-typed values. Illegal states should be unrepresentable in the type system rather than guarded against only at runtime.
Once Claude had a few examples to follow, it started producing significantly better type annotations and more expressive domain models.
Pre-commit hooks proved to be the first line of defence, catching issues before they ever reached CI. Linters, formatters, and all three type checkers ran automatically on every commit, providing rapid feedback and keeping the codebase consistent throughout the experiment.
To avoid spending time hand-crafting the configuration, I used pc-init to generate a strict .pre-commit-config.yaml tailored for modern Python projects. This ensured that formatting, linting, and type checking became part of the development workflow rather than an afterthought.
What Didn't Work
Claude struggled with this instruction:
All Python code MUST be fully type-annotated; untyped function signatures and untyped module-level values are not permitted.
Instead of fixing missing annotations, it occasionally attempted to disable checks in pyproject.toml.
Some manual intervention and code review were required to steer it back towards the desired standards.
The experience reinforced an observation I've made repeatedly with coding agents:
Agents optimize for making the error disappear, not necessarily for preserving your engineering constraints.
If you care about those constraints, you still need strong feedback loops.
Type Checker Observations
Running all three type checkers together was still faster than a single mypy run.
Interestingly, they complemented each other rather than duplicating effort:
-
tyfound some issues the others missed. -
pyreflyfound different classes of problems. -
zubanfelt the closest tomypyand was by far the easiest to configure.
The newer type-checking ecosystem is still catching up with mypy in terms of documentation and examples, so reaching the level of strictness I wanted involved a fair amount of experimentation.
Prize Categories
Not submitting for any specific prize category.
The real prize was finding out how far AI-assisted, specification-driven development can be pushed before human review becomes the limiting factor. 😉️
Top comments (0)