From Git Log to Guided Tour

3 min read·Sep 9, 2026

Deployments were happening regularly. Users had no way to know what had changed. Writing release notes by hand meant a redeploy just to fix a typo in the text - so the release notes barely got written at all.


Turning Git History Into Release Notes

The generator is a CLI script a developer runs before a release. It reads the commit history and parses conventional commits - the feat(scope): message style prefixes the team already used - and turns them into a structured markdown file:

---
version: '1.94.7'
date: '2026-01-31T13:45:57.491Z'
---
### feat(Customers): Bulk export added [[tour:bulk-export]]
### feat(Calendar): Users info shown in calendar

Because the team already wrote conventional commits for other reasons - changelogs, semantic versioning, PR hygiene - there was no new habit to adopt. The generator just made use of information that already existed. A developer reviews and edits the generated file before publishing, so nothing goes out unreviewed, but nobody starts from a blank page anymore.

The file is rendered in-app through a custom Next.js markdown renderer, so release notes live inside the product itself instead of a separate changelog page nobody visits.


A Markdown Token That Launches a Tour

The part worth explaining is [[tour:bulk-export]].

That token compiles to a Start Tour button, right inside the release note. Click it, and an interactive walkthrough launches - highlighting the exact UI elements the release note is describing, on the exact page they live on.

The tours themselves are built on driver.js, with a layer added on top to handle a real-world problem driver.js doesn't solve out of the box: what happens when the target element isn't on screen yet. A tour step often needs to wait for data to load before its target element even exists in the DOM - so the wrapper shows a loading state and waits for the element to appear before advancing, rather than failing silently or highlighting nothing.

Each tour is registered ahead of time, independently of the changelog:

import { TourConfig, registerTour } from '@components/tour';
import { t } from 'i18next';
 
const config: TourConfig = {
  access: 'crm.customers.view',
  steps: [
    {
      url: '/crm/customers/2184',
      element: `[data-tour='bulk-export-section']`,
      title: t('Bulk export'),
      content: t('You can export multiple customers at once from here'),
    },
    {
      element: `[data-tour='bulk-export-button']`,
      title: t('Bulk export'),
      content: t('Select a format and start the export'),
    },
  ],
};
 
registerTour('bulk-export', config);

Each step can declare its own url, so a single tour can walk a user across multiple pages - navigating them there automatically as part of the tour, not just pointing at things on the current screen. The access field gates the tour the same way it gates a page, so a tour never shows a user a feature they don't have permission to see. Once a tour is registered under an ID, [[tour:that-id]] in a changelog entry is all it takes to attach it to a release note.


The Changelog Is the Onboarding

This is the part that made the two pieces worth building together. A traditional product tour needs its own trigger - a "take a tour" button somewhere, a modal on first login, something a user has to go looking for. Attaching tours directly to changelog entries means the tour appears exactly when it's relevant: right after a feature ships, right where the user is reading about it.

There's no separate onboarding flow to design or maintain. The release notes already tell users what's new. The tour just makes "what's new" something they can click into immediately, instead of having to figure out on their own.


Results

  • 509 versions shipped through this system
  • 6 interactive tours linked directly from release notes
  • Zero hardcoded release notes - no redeploys just to update text
  • No separate onboarding flow - the changelog does that job too