Inspiration
We make printable murder mystery games. About half of them need a game master: somebody to read the clues out, keep time, move the table along, and stay out of the guessing. In the early '90s, VCR board games had an answer to that: the host lived on the videotape and interrupted you. That format faded with the VCR. VCR Party Host brings it back as a web page you open on the TV.
What it does
- A title screen with tracking lines and a red elapsed-time clock. While the tape plays, a blinking ▶ PLAY and a tape counter appear in the corner.
- PLAY loads the tape: six rounds at Quillmoor Manor, an original case for four players. A host character stands on screen while each clue is typed out.
- Timed interruptions stop the table ("STOP THE TAPE! Each player must choose a suspect to watch…") with a short sting synthesised by the Web Audio API. The clock, the other interruption timers and a clue that was still being typed out wait behind the alert, and play resumes where it stopped on DISMISS.
- PLAY / PAUSE / STOP / REW behave like a deck: PAUSE freezes the clock, the interruption timers and the typing, STOP asks before it throws the game away, REW goes back to the title and the next game starts from 00:00:00.
- After round six the table names a suspect, then the tape reveals the solution and an epilogue.
- Static HTML, CSS and ES modules. No runtime network requests, no dependencies, no media files. Interruptions are also announced through an ARIA live region, and text/background contrast is checked against WCAG AA in the tests.
How we built it — how Kiro was used
Kiro CLI, non-interactive. Kiro CLI 2.37 ran with --no-interactive. The spec and most of the implementation came from the default agent with --trust-tools=fs_read,fs_write. From the hook tests onward (the hook tests, the later bug fixes and the file:// correction), runs used the V3 agent (--v3), mostly with Claude Haiku 4.5, and V3 tool IDs (read_file, grep_search, str_replace and so on). We never passed --trust-all-tools, so Kiro could read and edit files but not run shell commands. The whole build ran on the Kiro Free plan.
Spec-driven development. We wrote a one-page brief (BRIEF.md: the idea, the constraints, original content only, static hosting). In one non-interactive chat run (not the interactive /spec flow), Kiro turned it into a feature spec in /.kiro/specs/vcr-party-host: requirements.md with 10 requirements and 45 acceptance criteria, design.md with the module layout, data model and test strategy, and tasks.md with 22 tasks in 8 phases; 18 of the tasks name the acceptance criteria they satisfy, and the other four point to sections of the design. Writing the spec took that one run and 1.86 credits. Kiro then implemented T-01 to T-20 and T-22 over about a dozen runs, a few tasks per run. T-21, the manual run-through in a browser, was ours. Compared with prompting for code directly, every implementation run named its tasks (later, the bug to fix), the acceptance criteria to meet and the tests to pass, and every bug we found later went back into tasks.md under T-21 with its root cause and fix, so the spec stayed the record of the project.
Steering. Three steering files: product.md (purpose, audience, the host's tone, terse VCR labels like PLAY and DISMISS), tech.md (hard constraints: no build step, no network requests at runtime, no runtime dependencies, node:test only) and structure.md (file tree and what each module owns). The constraints held: in Chrome the published app loads only its own files, and package.json has no dependencies.
Agent hooks. Three hooks in /.kiro/hooks:
run-tests—PostFileSaveonsrc/ortests/, a command hook that runsnode --test tests/*.test.js. We checked that it fires from the CLI: after Kiro editedsrc/timer.js, the hook ran the suite (131 passing at that point).original-content-guard—PostFileSaveondata/story.js, an agent hook that asks Kiro to look for real people, brands and existing works in the story. After a save ofstory.jsit ran and reported no matches. We still searched every name ourselves.spec-sync—PostTaskExec, an agent hook that checks a finished task's acceptance criteria against the tests. In the CLI, task-execution events come from/spec runin the interactive UI; we ran Kiro non-interactively, so we never saw this one fire.
Kiro's first attempt at hooks put them in .kiro/agents/ with a 2.x-style trigger and called a CLI option that does not exist (kiro-cli chat --message). We read the hooks documentation and had Kiro rewrite them as standalone .kiro/hooks/*.json files. We also learned that V3's --trust-tools matches exact V3 tool IDs, and that editing an existing file is str_replace (fs_write only creates or overwrites a whole file): edits were denied until we added str_replace.
What we did not use. Custom agents, MCP, property-based testing and powers. Kiro IDE and Kiro Crew were not used.
Who did what. Kiro CLI wrote the spec, the steering files, the hooks and the application code. Claude Code, an AI coding agent, wrote the brief and every prompt, ran Kiro CLI, played the app in Chrome and decided what to fix. A few changes were made directly rather than through Kiro: LICENSE, the README's license section and its tested-browsers line, .gitignore, audio/README.txt, and renaming the manor from Wraithwood to Quillmoor across six files (the first name clashed with an existing game). On November 1 we also repaired three of Kiro's new tests, put back one README line Kiro had changed by mistake, and tidied tasks.md where Kiro's additions landed in the wrong place or broke a sentence, with a note there about these repairs (details below).
Challenges we ran into
Four bugs only showed up in a real browser. Kiro fixed each one and added a regression test:
- Nothing appeared after PLAY: inline
display:noneon the screen sections beat the.activeclass. - Round 1's title and clue were empty: the render guards did not fire when the phase first became "playing".
- After STOP, the title screen kept the old elapsed time.
- Ending a round on its first clue skipped the next round's first clue. We caught this one on contact sheets of the demo video (one frame every four seconds); the new test fails on the old code and passes on the fix.
A review of the timing code against the acceptance criteria found four more, all now fixed by Kiro with regression tests that fail on the old code:
- An interruption that arrived while a clue was being typed left the clue cut off after DISMISS (AC-04.4). The typewriter can now pause and resume.
- The clock and the other interruption timers kept running behind the alert (AC-04.3, AC-05.2). The timer now keeps a set of pause reasons (the host, an interruption, a hidden tab), which also stopped a hidden-then-visible tab from undoing a PAUSE.
- Pausing twice in one round delayed the interruption: one due at 30 s fired at 40 s.
- After REW or PLAY AGAIN the clock and pending timers kept running, so the next game did not start at 00:00:00, and a last-round interruption could appear on the accusation screen.
We replayed 5–8 in headless Chromium with a controlled clock (for 8, the PLAY AGAIN path): 14 of 14 checks passed on the fixed build, locally before we pushed it and on GitHub Pages afterwards, and the same script fails 5–8 on the previous build. One run also taught us something about the setup: Kiro had no shell, so it could not run the tests it wrote, and it reported success while four of them failed (a missing test( line, two renderer tests that misused node:test mock timers and the PAUSE toggle, and a real double-start in the new resume()). Kiro fixed the resume() bug; we repaired the test code by hand. The same round deleted one existing test by mistake; a review caught it and Kiro restored it.
The spec also made a promise the platform could not keep. It promised the app would run from file://, but browsers block ES module scripts from that origin. We confirmed it in headless Chrome and changed acceptance criterion AC-09.2 to require a static HTTP host.
Accomplishments that we're proud of
- A complete evening from title to solution, playable from a static page.
- 145 tests on Node's built-in runner and zero dependencies.
- Every bug fix is recorded in the spec under T-21, the run-through task, with its root cause.
What we learned
- A spec is most useful as a ledger: each bug we found went back into
tasks.mdwith its cause and fix, and when the spec itself was wrong (file://), we changed the acceptance criterion instead of bending the code. - Hooks are only as good as their trigger. Check that each one actually fires on the surface you use.
- A run-through has to compare what is on screen before and after each step, not just whether the screen changed.
- An agent that cannot run the tests cannot know they pass. Run them yourself after every run, and check that each new regression test fails on the old code.
What's next for VCR Party Host
- More cases: the engine reads one story file, so a new evening is a new
story.jsplus the title card inindex.html. - Testing in Firefox and Safari and on a living-room TV.
Built With
- css3
- github
- html5
- javascript
- kiro
- node.js
- web-audio-api
Log in or sign up for Devpost to join the conversation.