Skip to content

Tour

Try the interactive example ↓

Product tours and onboarding progress tracking. Platform admins publish versioned tour definitions - ordered steps with semantic UI targets, prerequisites, branching rules, A/B variants, and reset policies - and clients record progress events while the server tracks per-user completion flags server-side, so progress follows the user across devices.

When to reach for it: onboarding checklists that resume correctly on any device, feature-discovery tours, "saw this panel" UI flags, tutorials that branch by experiment variant or prior choices, drop-off analytics on activation funnels.

When not to: gameplay progression with objectives and reward payouts belongs to Quest; one-off milestone badges belong to Achievements; gating features by environment or audience belongs to Remote Config - tour flags record what a user has seen, not what they may use.

Definitions, user progress, versioned flags and reset participation are stored in PostgreSQL through Rent. A progress event commits its flags and completion record together. Existing analytics counters stay in Redis; reward and Timeline effects run after the SQL commit and keep their existing best-effort behavior.

Concepts

  • Definitions vs progress - a tour definition (steps, branching, variants) is admin-owned and versioned; progress is computed per user from durable completion flags, scoped to one definition version.
  • Steps carry completion flags - each step names a completion_flag; completing the step records that flag. Only declared flags are accepted, and flags from one definition version cannot change another version's progress.
  • Prerequisites gate availability - a step becomes available only once every flag in its prerequisites list is set; completing out of order is rejected, not reordered.
  • The current step is computed - branching rules are evaluated first; if none match, the first available step in index order is current.
  • Versions - replacing a definition's steps bumps its version and applies the reset policy to every participant.
  • Variants - deterministic A/B bucketing (SHA-256 of experiment, user, and version): stable within a version, re-rolled on version change.
  • Admin vs user - defining, updating, deleting, analytics, and acting on another user's progress require a platform admin token.

Define a tour

Creation takes a JSON file with the full definition and requires a platform admin token:

snug tour create --file new-user-setup.json
# {"tour_id": "0199e100-0000-7000-8000-cb162dd0cb95", "name": "docs4_new_user_setup", "version": 1, ...}

snug tour list --query setup --filter 'status[eq]=active'
snug tour get --tour 0199e100-0000-7000-8000-cb162dd0cb95
snug tour update --tour 0199e100-0000-7000-8000-cb162dd0cb95 --status paused
snug tour delete --tour 0199e100-0000-7000-8000-cb162dd0cb95

The tour_id is server-generated - keep it from the create response; the name is a searchable label, not an address. The definition file format has its own page: Defining tours. Listing speaks the shared search query grammar and shows only active tours to non-admins; admins can pass --include-versions to also see draft, paused, and deprecated definitions.

Record progress events

Clients report what the user did; the server derives everything else:

snug tour event --tour 0199e100-0000-7000-8000-cb162dd0cb95 --event-type started
snug tour event --tour 0199e100-0000-7000-8000-cb162dd0cb95 --event-type step_completed --step step_welcome

Event types are started, step_seen, step_completed, flag_set, dismissed, and abandoned. Over HTTP the same call is a POST returning 202 with the recomputed progress; captured live:

curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"event_type": "step_completed", "step_id": "step_welcome"}' \
  http://localhost:4000/api/v1/tours/0199e100-0000-7000-8000-cb162dd0cb95/events
{
  "status": 202,
  "msg": "Accepted",
  "data": {
    "tour_id": "0199e100-0000-7000-8000-cb162dd0cb95",
    "definition_version": 1,
    "current_step_id": "step_profile",
    "completed_steps": ["step_welcome"],
    "mandatory_complete": false
  }
}

Completing a step whose prerequisites are unmet fails with 400 prerequisites_not_met; an unknown step_id is 404 step_not_found (both reproduced). Event metadata is free-form JSON but capped - a 9 KB payload was rejected with 400 limit_exceeded.

Read progress

snug --output json tour status docs-wave --tour 0199e100-0000-7000-8000-cb162dd0cb95
{
  "state": "in_progress",
  "current_step_id": "step_profile",
  "available_steps": ["step_profile", "step_advanced"],
  "completed_steps": ["step_welcome"],
  "seen_flags": ["saw_welcome"],
  "variant": "power",
  "mandatory_complete": false, "...": "..."
}

The CLI requires the user argument; pass your own subject. Over HTTP, GET /api/v1/tours/{tour_id}/status defaults to the caller, and asking for someone else with ?user= is admin-only - a regular token gets 403 insufficient_permissions (reproduced). States are not_started, in_progress, completed, reset, and blocked; the moment every mandatory step's flag is set, state flips to completed even with non-mandatory steps remaining.

UI completion flags

Flags can also be set or cleared directly, without a step event:

snug tour flag-set --tour 0199e100-0000-7000-8000-cb162dd0cb95 --flag sent_invite
snug tour flag-set --tour 0199e100-0000-7000-8000-cb162dd0cb95 --clear sent_invite

Only flags declared in the definition exist - an undeclared name is 404 flag_not_found (reproduced) - and admins may pass --user to act on another user.

Resets

snug tour reset --tour 0199e100-0000-7000-8000-cb162dd0cb95 --scope user      # yourself
snug tour reset --tour 0199e100-0000-7000-8000-cb162dd0cb95 --scope version   # admin only
snug tour reset --tour 0199e100-0000-7000-8000-cb162dd0cb95 --scope tour      # admin only

user scope resets the caller (admins may target --user someone else); version and tour scopes sweep every participant and require a platform admin token - a regular token gets 403 insufficient_permissions (reproduced). Version-change resets happen automatically; see reset policies for what each mode actually preserves.

Funnel analytics

Analytics require a platform admin token; a regular token gets 403 insufficient_permissions (reproduced):

snug tour analytics --tour 0199e100-0000-7000-8000-cb162dd0cb95                      # all-time
snug tour analytics --tour 0199e100-0000-7000-8000-cb162dd0cb95 --window 2026-08-28  # one day

The response reports started/completed totals, per-step seen/completed/drop_off, per-variant completion rates, and how often each branching rule fired. Counters are pre-aggregated into an all-time window ("all") plus daily YYYY-MM-DD windows, scoped per definition version (--version selects an older one; the active version is default).

Limits and configuration

Definitions are bounded at 200 steps, 512 flags, 100 branch rules, and 8 KiB of event metadata by default - tunable via the TOUR_* variables.

Reference

Interactive example

Guide people through meaningful steps in your app and remember their progress. Resume the same tour across devices without repeating completed steps.

An illustrative example that runs in your browser. No account or API calls required.