Dimitri Kurbatov Hiring
All work

Case study · Content Tool

One edit, every game updated

The Go Game's game content was duplicated everywhere, so one typo had to be fixed in several places. I built the Content Tool, where copies share their content and every edit reaches every game that uses it.

Client
The Go Game
Role
Lead engineer, largest contributor
Timeline
2022–2026
Platform
Web, internal

20,000+activities in the library

1edit to update every copy and live game

45activity types imported from Weve games

The problem

Every event at The Go Game runs on a game: a template of blocks and activities. The old workflow was heavily denormalized, so the same question lived in several places, and a typo fixed in one stayed wrong in the rest.

What I built

The Content Tool is the internal CMS for every game The Go Game runs:

  • A shared library. Activities, with their steps and media, are the building blocks. Blocks group activities, and templates arrange activities and blocks.
  • AI Assist. Describe a block in a prompt, pick the activity type, number of options and difficulty, and the AI drafts it.
  • A rich-text editor for game copy, and importers that bring 45 activity types in from existing Weve games.

How edits reach every copy

The core design is that copies share content instead of duplicating it, and every change finds its way up to the games that use it.

  • Shallow copies. Copying a template for an event copies only the template and its join rows: order, positions and the relations between items. The copy points at the same activities, so a fix to an activity is a fix in every copy.
  • Copy-on-write. Editing a shared activity inside one template clones it into a copy scoped to that template, linked back to its origin. Custom changes stay local and the library stays clean.
  • A change feed through an outbox. A PostgreSQL trigger on every content table writes each change to a notification table and sends pg_notify with just the row id. A deduplicated Bull job loads the full row and publishes it on Redis.
  • Version bumps that climb the graph. Each listener only knows its direct parent and bumps that row's version. The bump is itself a change, so the trigger fires again and it climbs one level at a time:
    1. step media → step
    2. step → activity
    3. activity → every block and template that uses it
    4. block → every template that uses it
    5. template → the live game
  • Coalesced re-exports. When a template's version changes, it's exported to the live game runtime. There is one export in flight per template plus one trailing pass, so a burst of edits costs at most two exports and the last state always lands.

The sharp edge is fan-out: a shared row touches everything that uses it. When a save once rewrote shared activity rows by accident, the version bumps rippled through every template that used them. The rule since is that saves load only join rows, never the shared activities themselves.

Outcome

Creators fix things once. One edit to an activity reaches every template, every event copy and every live game that uses it, which removed most of the repetitive updates and the maintenance time they cost.

Screens

Content Tool in use

Building something like this?

Ask my AI (opens in a new tab)