Skip to content

Track tutorial exit and play-now in GA4 - #79

Open
adhocmedia wants to merge 3 commits into
MUME:masterfrom
adhocmedia:ga-tutorial-exit-events
Open

adhocmedia wants to merge 3 commits into
MUME:masterfrom
adhocmedia:ga-tutorial-exit-events

Conversation

@adhocmedia

@adhocmedia adhocmedia commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Adds two GA4 events on the interactive tutorial so we can measure the newbie funnel properly.

Events

  • exit_tutorial — fires when a player clicks Exit Tutorial (left early).
  • tutorial_complete_play — fires on Play MUME Now at the end (both the end screen and the completion modal).

Both include a chapter parameter (which chapter they were on). GA4 is already set up in the theme (G-LL4RX9KM6Q, consent-aware), so this only sends named events via the existing window.gtag; no base analytics or config changes, and navigation is unaffected (best-effort, wrapped in try/catch).

After merge/deploy, mark exit_tutorial and tutorial_complete_play as Key events in GA4 to track them as conversions. The ratio of the two gives the skip-vs-finish rate.

🤖 Generated with Claude Code

Summary by Sourcery

Add consent-compatible GA4 funnel tracking for tutorial exits and transitions into gameplay.

New Features:

  • Track early tutorial exits and completed tutorial play-throughs as GA4 events, including the current chapter.

Enhancements:

  • Ensure analytics tracking distinguishes early exits from exits after tutorial completion and does not affect navigation.

Fire two GA4 events so the newbie funnel can tell early exits from finishers:
- exit_tutorial — when a player clicks Exit Tutorial
- tutorial_complete_play — when they click Play MUME Now at the end (screen + modal)

Both carry the current chapter. GA is already configured in the theme, so this
only sends named events; nothing else changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Updates the tutorial player to emit chapter-scoped GA4 events for early exits and completion-driven play actions, while maintaining navigation reliability and correctly handling completion state across chapters.

Sequence diagram for tutorial funnel analytics events

sequenceDiagram
    participant Player
    participant TutorialPlayer
    participant GA4 as window.gtag
    participant Destination

    alt Player exits before completion
        Player->>TutorialPlayer: click Exit Tutorial
        TutorialPlayer->>GA4: gtag('event', 'exit_tutorial', chapter)
        TutorialPlayer->>Destination: navigate to Newcomers Hub
    else Player selects Play MUME Now after completion
        Player->>TutorialPlayer: click Play MUME Now
        TutorialPlayer->>GA4: gtag('event', 'tutorial_complete_play', chapter)
        TutorialPlayer->>Destination: navigate to Web Client
    end
Loading

State diagram for tutorial completion tracking

stateDiagram-v2
    [*] --> InProgress
    InProgress --> Completed: openModal()
    InProgress --> InProgress: trackExit() / exit_tutorial
    Completed --> Completed: trackExit() / no event
    Completed --> InProgress: route.path changes
Loading

File-Level Changes

Change Details Files
Add best-effort GA4 funnel event tracking for tutorial exits and completion-driven play actions.
  • Send exit_tutorial with the current chapter when the Exit control is clicked before completion.
  • Send tutorial_complete_play with the current chapter when Play MUME Now is selected from the completion modal.
  • Use the existing optional window.gtag integration and catch analytics failures so navigation remains unaffected.
docs/.vitepress/theme/components/TutorialPlayer.vue
Track tutorial completion state to distinguish early exits from post-completion navigation.
  • Mark the tutorial complete when the end-of-tutorial modal opens, suppressing later exit events.
  • Reset completion state when SPA chapter navigation occurs so exits during a new chapter are tracked correctly.
docs/.vitepress/theme/components/TutorialPlayer.vue

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="docs/.vitepress/theme/components/TutorialPlayer.vue" line_range="711" />
<code_context>
           </button>

-          <a :href="withBase(NEWCOMERS_URL)" class="tut-action-btn tut-exit-btn" title="Exit Tutorial to Newcomers Hub" aria-label="Exit Tutorial">
+          <a :href="withBase(NEWCOMERS_URL)" class="tut-action-btn tut-exit-btn" title="Exit Tutorial to Newcomers Hub" aria-label="Exit Tutorial" @click="trackTutorial('exit_tutorial')">
             <i class="fa fa-sign-out" aria-hidden="true"></i>
             <span class="tut-btn-text">Exit</span>
</code_context>
<issue_to_address>
**Finishers counted as early exits**

When a player completes the final chapter and then clicks the always-visible Exit link, `trackTutorial('exit_tutorial')` fires regardless of completion state, so completed players are recorded as early exits and the skip-versus-finish metric is skewed.

Guard the exit event with completion state so it fires only before the tutorial is complete.
</issue_to_address>

### Comment 2
<location path="docs/.vitepress/theme/components/TutorialPlayer.vue" line_range="789" />
<code_context>

                 <div class="tut-end-actions">
-                  <a class="tut-enter" :href="withBase(BROWSER_PLAY_URL)">Play MUME Now (Web Client) &rarr;</a>
+                  <a class="tut-enter" :href="withBase(BROWSER_PLAY_URL)" @click="trackTutorial('tutorial_complete_play')">Play MUME Now (Web Client) &rarr;</a>
                   <a class="tut-secondary-link" :href="withBase(NEWCOMERS_URL)">Explore Newcomers Guide</a>
                   <a class="tut-secondary-link" :href="withBase(PLAY_HUB_URL)">Back to Play Hub</a>
</code_context>
<issue_to_address>
**Completion play link never appears**

When a player expects to use the end-screen Play MUME Now link, `renderStepLog()` and the step handlers never produce an `end` item, so the `b.kind === 'end'` branch never renders the link and its `tutorial_complete_play` handler cannot fire; the completion click goes untracked.

Add an `end` item when the final chapter is completed so the link renders and its handler can fire.
</issue_to_address>

### Comment 3
<location path="docs/.vitepress/theme/components/TutorialPlayer.vue" line_range="114" />
<code_context>
+function trackTutorial(name: string) {
+  try {
+    ;(window as any).gtag?.('event', name, { event_category: 'tutorial', chapter: chapterNum.value })
+  } catch { /* analytics is best-effort, never block navigation */ }
+}
+
</code_context>
<issue_to_address>
**Analytics failures go unnoticed**

When `window.gtag` throws while a player clicks one of the tracked links, `trackTutorial` suppresses the exception and continues navigation, so the event is dropped with no diagnostic for operators; the tutorial funnel is silently undercounted.

Report the caught error through a non-blocking diagnostic while continuing navigation.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 3 findings to address first, and clicking either link sends the event name and current chapter number to the existing GA4 property, where those records can remain after a revert; reverting stops future events but does not remove past ones. The impact is limited to bounded analytics data, not a change to navigation or game behavior.

Blocking findings: docs/.vitepress/theme/components/TutorialPlayer.vue:711, docs/.vitepress/theme/components/TutorialPlayer.vue:789, docs/.vitepress/theme/components/TutorialPlayer.vue:114


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread docs/.vitepress/theme/components/TutorialPlayer.vue Outdated

<div class="tut-end-actions">
<a class="tut-enter" :href="withBase(BROWSER_PLAY_URL)">Play MUME Now (Web Client) &rarr;</a>
<a class="tut-enter" :href="withBase(BROWSER_PLAY_URL)" @click="trackTutorial('tutorial_complete_play')">Play MUME Now (Web Client) &rarr;</a>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Completion play link never appears

When a player expects to use the end-screen Play MUME Now link, renderStepLog() and the step handlers never produce an end item, so the b.kind === 'end' branch never renders the link and its tutorial_complete_play handler cannot fire; the completion click goes untracked.

Add an end item when the final chapter is completed so the link renders and its handler can fire.

Prompt for AI agents
In `docs/.vitepress/theme/components/TutorialPlayer.vue` at line 789:

**Completion play link never appears**

When a player expects to use the end-screen Play MUME Now link, `renderStepLog()` and the step handlers never produce an `end` item, so the `b.kind === 'end'` branch never renders the link and its `tutorial_complete_play` handler cannot fire; the completion click goes untracked.

Add an `end` item when the final chapter is completed so the link renders and its handler can fire.

Comment thread docs/.vitepress/theme/components/TutorialPlayer.vue Outdated
- Guard exit_tutorial behind a tutorialCompleted flag (set when the end-of-tutorial
  modal opens), so finishers who later click Exit are not counted as early exits.
- Drop the @click on the dead end-screen link (the 'end' beat is never generated,
  so that branch never renders); completion is tracked on the modal's Play button.
- Log caught analytics errors via console.debug instead of swallowing them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@adhocmedia

Copy link
Copy Markdown
Contributor Author

Thanks, addressed all three:

  1. Finishers counted as exits — exit_tutorial now fires via a trackExit() guard that checks a tutorialCompleted flag (set when the end-of-tutorial modal opens), so a completed player clicking Exit is not logged as an early exit.
  2. Completion link — correct that the b.kind === 'end' branch never renders (no end beat is generated). Completion is surfaced by the modal, whose Play MUME Now button already carries tutorial_complete_play, so completion is tracked. I removed the pointless @click on the dead end-screen link rather than wire up a new end beat.
  3. Silent analytics failure — the catch now logs via console.debug instead of swallowing.

@adhocmedia

Copy link
Copy Markdown
Contributor Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="docs/.vitepress/theme/components/TutorialPlayer.vue" line_range="98" />
<code_context>
 const isSheetOpen = ref(false)
 const isExpanded = ref(false)
 const isModalOpen = ref(false)
+const tutorialCompleted = ref(false)
 const activeModalImage = ref<{ src: string; alt: string } | null>(null)

</code_context>
<issue_to_address>
**Retake exits go uncounted**

When a player completes the tutorial, navigates to another chapter, and clicks Exit, the route watcher rerenders the chapter without resetting `tutorialCompleted`, so `trackExit()` suppresses `exit_tutorial` and GA4 misses the retake exit.

Reset `tutorialCompleted` when the route watcher loads another chapter.

Also at `docs/.vitepress/theme/components/TutorialPlayer.vue:105`.
</issue_to_address>

### Comment 2
<location path="docs/.vitepress/theme/components/TutorialPlayer.vue" line_range="117" />
<code_context>
+// the theme; this only sends a named signal with the chapter they were on.
+function trackTutorial(name: string) {
+  try {
+    ;(window as any).gtag?.('event', name, { event_category: 'tutorial', chapter: chapterNum.value })
+  } catch (err) {
+    // Analytics is best-effort and must never block navigation; surface it quietly.
</code_context>
<issue_to_address>
**Navigation drops queued tutorial events**

When a player clicks Exit or Play before the asynchronously loaded GA script has processed the queued event, `trackTutorial` only queues the event in the page's in-memory `dataLayer`, while the Exit and Play anchors immediately navigate away. If the async GA script has not consumed and sent the queued event, unloading the page discards it and GA4 misses the conversion.

Send the event using a navigation-safe mechanism, or wait for GA's event callback before allowing the anchor navigation.

Also at `docs/.vitepress/theme/components/TutorialPlayer.vue:724`, `docs/.vitepress/theme/components/TutorialPlayer.vue:904`.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 2 findings to address first, and each Exit or Play Now click sends a chapter-tagged event to GA4, so an incorrect event or chapter can leave misleading analytics records that reverting the code will not retract. The impact is limited to those tracking records; this adds no user-identifying data in the diff.

Blocking findings: docs/.vitepress/theme/components/TutorialPlayer.vue:98, docs/.vitepress/theme/components/TutorialPlayer.vue:117


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

const isSheetOpen = ref(false)
const isExpanded = ref(false)
const isModalOpen = ref(false)
const tutorialCompleted = ref(false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Retake exits go uncounted

When a player completes the tutorial, navigates to another chapter, and clicks Exit, the route watcher rerenders the chapter without resetting tutorialCompleted, so trackExit() suppresses exit_tutorial and GA4 misses the retake exit.

Reset tutorialCompleted when the route watcher loads another chapter.

Also at docs/.vitepress/theme/components/TutorialPlayer.vue:105.

Prompt for AI agents
In `docs/.vitepress/theme/components/TutorialPlayer.vue` at line 98:

**Retake exits go uncounted**

When a player completes the tutorial, navigates to another chapter, and clicks Exit, the route watcher rerenders the chapter without resetting `tutorialCompleted`, so `trackExit()` suppresses `exit_tutorial` and GA4 misses the retake exit.

Reset `tutorialCompleted` when the route watcher loads another chapter.

Also at `docs/.vitepress/theme/components/TutorialPlayer.vue:105`.

// the theme; this only sends a named signal with the chapter they were on.
function trackTutorial(name: string) {
try {
;(window as any).gtag?.('event', name, { event_category: 'tutorial', chapter: chapterNum.value })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Navigation drops queued tutorial events

When a player clicks Exit or Play before the asynchronously loaded GA script has processed the queued event, trackTutorial only queues the event in the page's in-memory dataLayer, while the Exit and Play anchors immediately navigate away. If the async GA script has not consumed and sent the queued event, unloading the page discards it and GA4 misses the conversion.

Send the event using a navigation-safe mechanism, or wait for GA's event callback before allowing the anchor navigation.

Also at docs/.vitepress/theme/components/TutorialPlayer.vue:724, docs/.vitepress/theme/components/TutorialPlayer.vue:904.

Prompt for AI agents
In `docs/.vitepress/theme/components/TutorialPlayer.vue` at line 117:

**Navigation drops queued tutorial events**

When a player clicks Exit or Play before the asynchronously loaded GA script has processed the queued event, `trackTutorial` only queues the event in the page's in-memory `dataLayer`, while the Exit and Play anchors immediately navigate away. If the async GA script has not consumed and sent the queued event, unloading the page discards it and GA4 misses the conversion.

Send the event using a navigation-safe mechanism, or wait for GA's event callback before allowing the anchor navigation.

Also at `docs/.vitepress/theme/components/TutorialPlayer.vue:724`, `docs/.vitepress/theme/components/TutorialPlayer.vue:904`.

Chapter navigation is SPA (router.go), so the component is not remounted and
tutorialCompleted would persist. Reset it in the route watcher so a retake-then-exit
is still counted as an exit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@adhocmedia

Copy link
Copy Markdown
Contributor Author

Round 2, thanks:

  1. Retake exits uncounted — valid. Chapter nav is SPA (router.go), so the component isn't remounted and tutorialCompleted was persisting. I now reset it in the route.path watcher, so a retake-then-exit is counted again (and it's re-set to true only when the completion modal reopens).

  2. Navigation drops queued events — doesn't apply here. The premise is a full-document unload discarding the queued event, but Exit (/resources/newcomers) and Play (/play/browser) are both internal VitePress pages, so clicks are SPA navigations: the document doesn't unload and the queued gtag event is processed normally. Intercepting with preventDefault/event_callback would actually break VitePress's own link routing, so I've left the anchors as-is.

@adhocmedia

Copy link
Copy Markdown
Contributor Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Needs a human reviewer. If the events fire at the wrong time, GA4 will retain a bounded set of misclassified tutorial events; reverting stops new events but does not remove the historical data. No user-facing navigation or other behavior changes.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

This branch was successfully deployed

1 active deployment
pr-79 — 5b947119 Deployed Oct 18, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant