User Journeys logo

User Journeys

Community
alinaqi
user-journeys

User experience flows - journey mapping, UX validation, error recovery

Overview

Publisheralinaqi
Repositorymaggy
Skill nameuser-journeys
Stars
707
Forks
57
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by alinaqi on GitHub. Read the source before you install it.

Installation

Install the User Journeys AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/alinaqi/maggy/tree/main/skills/user-journeys
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/alinaqi/maggy.git /tmp/maggy
mkdir -p .claude/skills
cp -r /tmp/maggy/skills/user-journeys .claude/skills/user-journeys
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable User Journeys in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads User Journeys on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so User Journeys is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

User Journeys Skill

For defining and testing real user experiences - not just specs, but actual flows humans take through your application.


Philosophy

Specs test features. Journeys test experiences.

A feature can pass all specs but still deliver a terrible experience. User journeys capture:

  • How users actually navigate (not how we think they should)
  • Emotional states at each step (frustrated, confused, delighted)
  • Recovery from mistakes (users will make them)
  • Real-world conditions (slow networks, interruptions, distractions)

Journey Documentation Structure

_project_specs/
├── journeys/
│   ├── _template.md              # Journey template
│   ├── critical/                 # Must-work journeys (revenue, core value)
│   │   ├── signup-to-first-value.md
│   │   ├── checkout-purchase.md
│   │   └── login-to-dashboard.md
│   ├── common/                   # Frequent user paths
│   │   ├── browse-and-search.md
│   │   ├── update-profile.md
│   │   └── invite-team-member.md
│   └── edge-cases/               # Error recovery, unusual paths
│       ├── payment-failure-retry.md
│       ├── session-timeout-recovery.md
│       └── offline-reconnection.md

Journey Template

markdown
# Journey: [Name]

## Overview
| Attribute | Value |
|-----------|-------|
| **Priority** | Critical / High / Medium |
| **User Type** | New / Returning / Admin |
| **Frequency** | Daily / Weekly / One-time |
| **Success Metric** | Conversion rate, time to complete, drop-off rate |

## User Goal
What is the user trying to accomplish? Write from their perspective.

> "I want to [goal] so that I can [benefit]."

## Preconditions
- User state (logged in, has subscription, first visit)
- Data state (has items in cart, has team members)
- Environment (mobile, desktop, slow connection)

## Journey Steps

### Step 1: [Entry Point]
**User Action:** What the user does
**System Response:** What they should see/experience
**Success Criteria:**
- [ ] Page loads in < 2 seconds
- [ ] Primary CTA is immediately visible
- [ ] User understands what to do next

**Potential Friction:**
- Slow load time → Show skeleton/loader
- Unclear CTA → A/B test copy variations

---

### Step 2: [Next Action]
**User Action:** ...
**System Response:** ...
**Success Criteria:**
- [ ] ...

**Potential Friction:**
- ...

---

## Error Scenarios

### E1: [Error Name]
**Trigger:** What causes this error
**User Sees:** Error message/state
**Recovery Path:** How user gets back on track
**Test:** How to verify recovery works

## Metrics to Track
- Time to complete journey
- Drop-off rate at each step
- Error rate and recovery rate
- User satisfaction (if surveyed)

## E2E Test Reference
Link to Playwright test: `e2e/tests/journeys/[name].spec.ts`

Critical Journey Examples

Signup to First Value

markdown
# Journey: Signup to First Value

## Overview
| Attribute | Value |
|-----------|-------|
| **Priority** | Critical |
| **User Type** | New |
| **Frequency** | One-time |
| **Success Metric** | % reaching "aha moment" within 5 min |

## User Goal
> "I want to try this product quickly to see if it solves my problem."

## Preconditions
- First visit to site
- No account
- Came from landing page or ad

## Journey Steps

### Step 1: Landing Page
**User Action:** Clicks "Get Started Free" or "Try Now"
**System Response:** Signup form appears (modal or new page)
**Success Criteria:**
- [ ] CTA visible above fold
- [ ] No distracting elements
- [ ] Clear value proposition visible

**Potential Friction:**
- Too many form fields → Reduce to email + password only
- Social login missing → Add Google/GitHub options

### Step 2: Account Creation
**User Action:** Enters email and password (or uses social login)
**System Response:**
- Creates account
- Sends verification email (don't block on it)
- Redirects to onboarding

**Success Criteria:**
- [ ] Account created in < 3 seconds
- [ ] No email verification wall (verify later)
- [ ] Clear next step shown

**Potential Friction:**
- Email already exists → Offer login link
- Weak password → Show requirements inline, not after submit

### Step 3: Onboarding (Quick Win)
**User Action:** Completes 1-2 setup questions
**System Response:**
- Personalizes experience
- Shows progress indicator
- Leads to first action

**Success Criteria:**
- [ ] Max 3 questions
- [ ] Skip option available
- [ ] < 60 seconds total

**Potential Friction:**
- Too many questions → User abandons
- No skip option → User feels trapped

### Step 4: First Value (Aha Moment)
**User Action:** Completes core action (creates first X, sees first result)
**System Response:**
- Celebrates success
- Shows value delivered
- Suggests next step

**Success Criteria:**
- [ ] User experiences core value
- [ ] Completion feels rewarding
- [ ] Clear path to continue

## Error Scenarios

### E1: Email Already Registered
**Trigger:** User tries existing email
**User Sees:** "Already have an account? Log in or reset password"
**Recovery Path:** Click to login or reset
**Test:** `signup-existing-email.spec.ts`

### E2: Social Login Fails
**Trigger:** OAuth provider error
**User Sees:** "Couldn't connect. Try email signup or try again."
**Recovery Path:** Email signup form shown as fallback
**Test:** `social-login-failure.spec.ts`

## Metrics to Track
- Signup → First Value: Target < 5 min
- Drop-off at each step
- Social vs email signup ratio
- Skip rate on onboarding

Checkout Purchase

markdown
# Journey: Checkout Purchase

## Overview
| Attribute | Value |
|-----------|-------|
| **Priority** | Critical (Revenue) |
| **User Type** | Any |
| **Frequency** | Variable |
| **Success Metric** | Checkout completion rate |

## User Goal
> "I want to pay quickly and securely without surprises."

## Journey Steps

### Step 1: Cart Review
**User Action:** Views cart before checkout
**System Response:**
- Shows all items with images, prices
- Shows subtotal, taxes, shipping
- Clear "Checkout" CTA

**Success Criteria:**
- [ ] No hidden fees revealed later
- [ ] Easy to modify quantities
- [ ] Saved items visible

### Step 2: Checkout Start
**User Action:** Clicks "Checkout"
**System Response:**
- Shows checkout form or redirect to payment
- Progress indicator (Step 1 of 3)
- Order summary sidebar

**Success Criteria:**
- [ ] Guest checkout option
- [ ] Express checkout (Apple/Google Pay) prominent
- [ ] Form fields pre-filled if logged in

### Step 3: Payment
**User Action:** Enters payment info
**System Response:**
- Secure input fields (Stripe/payment provider)
- Real-time validation
- Clear "Pay $XX" button

**Success Criteria:**
- [ ] Card validation inline, not after submit
- [ ] Multiple payment options
- [ ] Security indicators visible

### Step 4: Confirmation
**User Action:** Submits payment
**System Response:**
- Processing indicator
- Success page with order details
- Email confirmation sent

**Success Criteria:**
- [ ] Confirmation within 5 seconds
- [ ] Order number clearly visible
- [ ] Next steps clear (shipping, access, etc.)

## Error Scenarios

### E1: Payment Declined
**Trigger:** Card declined by processor
**User Sees:** "Payment declined. Please try another card."
**Recovery Path:**
- Stay on payment step
- Pre-fill other fields
- Offer alternative payment methods
**Test:** `payment-declined-recovery.spec.ts`

### E2: Session Timeout During Checkout
**Trigger:** User away too long
**User Sees:** Cart preserved, re-auth required
**Recovery Path:**
- Quick login
- Return to same checkout step
- Cart contents intact
**Test:** `checkout-session-timeout.spec.ts`

Journey Testing with Playwright

Journey Test Structure

typescript
// e2e/tests/journeys/signup-to-value.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Journey: Signup to First Value', () => {
  test.describe.configure({ mode: 'serial' }); // Run in order

  test('Step 1: Landing page has clear CTA', async ({ page }) => {
    await page.goto('/');

    // CTA visible above fold without scrolling
    const cta = page.getByRole('button', { name: /get started|try free/i });
    await expect(cta).toBeVisible();
    await expect(cta).toBeInViewport();
  });

  test('Step 2: Can create account quickly', async ({ page }) => {
    await page.goto('/');
    await page.getByRole('button', { name: /get started/i }).click();

    // Minimal fields
    await expect(page.getByLabel('Email')).toBeVisible();
    await expect(page.getByLabel('Password')).toBeVisible();

    // Complete signup
    const startTime = Date.now();
    await page.getByLabel('Email').fill('newuser@example.com');
    await page.getByLabel('Password').fill('SecurePass123!');
    await page.getByRole('button', { name: /sign up|create/i }).click();

    // Should reach onboarding quickly
    await expect(page).toHaveURL(/onboarding|welcome|setup/);
    expect(Date.now() - startTime).toBeLessThan(5000); // < 5 seconds
  });

  test('Step 3: Onboarding is skippable', async ({ page }) => {
    // ... login as new user ...
    await page.goto('/onboarding');

    // Skip option exists
    const skipButton = page.getByRole('button', { name: /skip/i });
    await expect(skipButton).toBeVisible();
  });

  test('Step 4: Can reach first value in < 5 min', async ({ page }) => {
    // Full journey timing
    const journeyStart = Date.now();

    // ... complete full journey ...

    // Verify first value delivered
    await expect(page.getByText(/success|created|done/i)).toBeVisible();

    // Total time check
    const totalTime = (Date.now() - journeyStart) / 1000 / 60; // minutes
    expect(totalTime).toBeLessThan(5);
  });
});

Error Recovery Tests

typescript
// e2e/tests/journeys/checkout-recovery.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Journey: Checkout Error Recovery', () => {
  test('recovers from payment decline gracefully', async ({ page }) => {
    // Setup: Add item to cart, go to checkout
    await page.goto('/products');
    await page.getByTestId('add-to-cart').first().click();
    await page.getByRole('link', { name: 'Checkout' }).click();

    // Use Stripe test card that declines
    const stripeFrame = page.frameLocator('iframe[name*="stripe"]');
    await stripeFrame.getByPlaceholder('Card number').fill('4000000000000002');
    await stripeFrame.getByPlaceholder('MM / YY').fill('12/30');
    await stripeFrame.getByPlaceholder('CVC').fill('123');

    await page.getByRole('button', { name: /pay/i }).click();

    // Verify friendly error
    await expect(page.getByText(/declined|try another/i)).toBeVisible();

    // Verify still on checkout (not kicked out)
    await expect(page).toHaveURL(/checkout/);

    // Verify can try again with different card
    await stripeFrame.getByPlaceholder('Card number').fill('4242424242424242');
    await page.getByRole('button', { name: /pay/i }).click();

    // Should succeed now
    await expect(page).toHaveURL(/success|confirmation/);
  });

  test('preserves cart after session timeout', async ({ page, context }) => {
    // Add items to cart
    await page.goto('/products');
    await page.getByTestId('add-to-cart').first().click();

    // Clear session (simulate timeout)
    await context.clearCookies();

    // Return to site
    await page.goto('/cart');

    // Cart should be preserved (local storage or recovered)
    await expect(page.getByTestId('cart-item')).toHaveCount(1);
  });
});

User Experience Validation

UX Checklist per Journey Step

markdown
## UX Validation Checklist

### Clarity
- [ ] User knows where they are (breadcrumbs, progress)
- [ ] User knows what to do next (clear CTA)
- [ ] User knows what just happened (feedback)

### Speed
- [ ] Page loads < 2 seconds
- [ ] Actions complete < 3 seconds
- [ ] Progress shown for longer operations

### Forgiveness
- [ ] Mistakes are easy to undo
- [ ] Errors explain what went wrong
- [ ] Recovery path is clear

### Accessibility
- [ ] Keyboard navigation works
- [ ] Screen reader announces changes
- [ ] Focus management correct
- [ ] Color contrast sufficient

### Mobile
- [ ] Touch targets >= 44px
- [ ] No horizontal scroll
- [ ] Forms don't zoom unexpectedly
- [ ] Works on slow 3G

Automated UX Checks

typescript
// e2e/utils/ux-validators.ts
import { Page, expect } from '@playwright/test';

export async function validatePageLoad(page: Page, maxMs = 2000) {
  const timing = await page.evaluate(() => {
    const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
    return nav.loadEventEnd - nav.startTime;
  });
  expect(timing).toBeLessThan(maxMs);
}

export async function validateCTAVisible(page: Page, ctaText: RegExp) {
  const cta = page.getByRole('button', { name: ctaText });
  await expect(cta).toBeVisible();
  await expect(cta).toBeInViewport();
}

export async function validateNoLayoutShift(page: Page) {
  const cls = await page.evaluate(() => {
    return new Promise<number>((resolve) => {
      let clsValue = 0;
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          if (!(entry as any).hadRecentInput) {
            clsValue += (entry as any).value;
          }
        }
      });
      observer.observe({ type: 'layout-shift', buffered: true });
      setTimeout(() => {
        observer.disconnect();
        resolve(clsValue);
      }, 1000);
    });
  });
  expect(cls).toBeLessThan(0.1); // Good CLS score
}

export async function validateAccessibility(page: Page) {
  // Check focus visible on interactive elements
  const buttons = page.getByRole('button');
  const count = await buttons.count();

  for (let i = 0; i < Math.min(count, 5); i++) {
    await buttons.nth(i).focus();
    await expect(buttons.nth(i)).toBeFocused();
  }
}

Journey Metrics Dashboard

Track journey health with these metrics:

typescript
// lib/journey-metrics.ts
interface JourneyMetric {
  journey: string;
  step: string;
  timestamp: Date;
  duration: number;
  success: boolean;
  userId?: string;
}

// Track in your analytics (PostHog, Mixpanel, etc.)
export function trackJourneyStep(metric: JourneyMetric) {
  analytics.track('journey_step', {
    journey_name: metric.journey,
    step_name: metric.step,
    duration_ms: metric.duration,
    success: metric.success,
  });
}

// Example usage in app
const journeyStart = Date.now();
// ... user completes step ...
trackJourneyStep({
  journey: 'signup_to_value',
  step: 'account_creation',
  timestamp: new Date(),
  duration: Date.now() - journeyStart,
  success: true,
});

Common Journey Patterns

Progressive Disclosure Journey

User sees simple view first, complexity revealed as needed.

markdown
Step 1: Show basic options only
Step 2: "Advanced" expands more options
Step 3: Expert mode unlocks everything

Guided Setup Journey

Hand-hold new users through initial configuration.

markdown
Step 1: Welcome + single choice
Step 2: Core preference
Step 3: Optional integrations (skippable)
Step 4: First action with guidance
Step 5: Success + remove training wheels

Recovery Journey

User returns after failure or abandonment.

markdown
Step 1: Recognize returning user
Step 2: Restore previous state
Step 3: Acknowledge what happened
Step 4: Offer clear path forward
Step 5: Complete original goal

Anti-Patterns

  • Happy path only - Test error recovery, not just success
  • Spec-driven testing - Test user goals, not features
  • Ignoring time - Measure how long journeys take
  • Desktop-only - Test mobile journeys separately
  • Skipping emotions - Consider user frustration points
  • No metrics - Track journey completion and drop-off
  • Static journeys - Update as user behavior evolves

Quick Reference

Journey Priorities

PriorityCriteriaTest Frequency
CriticalRevenue, core valueEvery deploy
HighDaily user actionsDaily
MediumWeekly featuresWeekly
LowEdge casesOn change

Package.json Scripts

json
{
  "scripts": {
    "test:journeys": "playwright test e2e/tests/journeys/",
    "test:journeys:critical": "playwright test e2e/tests/journeys/critical/",
    "test:journeys:report": "playwright show-report"
  }
}

Journey Documentation Checklist

  • User goal clearly stated
  • All steps documented
  • Success criteria per step
  • Error scenarios covered
  • Recovery paths defined
  • Metrics identified
  • E2E test linked

Frequently asked questions

What does the User Journeys AI skill do?

User experience flows - journey mapping, UX validation, error recovery

Why use User Journeys on TypingMind?

Because you install it once and use it with any model. User Journeys is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install User Journeys in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/alinaqi/maggy/tree/main/skills/user-journeys. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use User Journeys?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with User Journeys?

As many as you like. As long as a model supports skills, you can use User Journeys with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the User Journeys AI skill free?

Yes. It is published on GitHub by alinaqi under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇