Guidely.Core 1.0.0

dotnet add package Guidely.Core --version 1.0.0
                    
NuGet\Install-Package Guidely.Core -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Guidely.Core" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Guidely.Core" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Guidely.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Guidely.Core --version 1.0.0
                    
#r "nuget: Guidely.Core, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Guidely.Core@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Guidely.Core&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Guidely.Core&version=1.0.0
                    
Install as a Cake Tool

Guidely.Core

A powerful, framework-agnostic tutorial state machine library for .NET applications. Build interactive, step-by-step guided experiences with intelligent flow control.

Features

  • Attribute-Driven Steps - Define tutorial steps as simple classes with [TutorialStep], [TutorialTarget], and [AutoAdvanceOn] attributes
  • Conditional Transitions - Create dynamic tutorial flows that adapt based on application state using a fluent builder API
  • Auto-Advance Triggers - Steps automatically advance when users complete actions (button clicks, form submissions, etc.)
  • Context-Aware - Track application state with strongly-typed context records for intelligent decision-making
  • Step Groups - Organize steps into logical sections (Introduction, Setup, Features, etc.) with ordering support
  • Back Navigation - Navigate to previous steps within the current group
  • Skippable Steps - Steps that require user action can show alternate content and a Next button when their goal is already achieved, with configurable skip targets
  • Group-to-View Mapping - Map tutorial groups to application views/pages for intelligent restart-from-current-view behavior
  • Persistence Ready - Implement ITutorialPersistence to save/restore tutorial progress across sessions
  • Framework Agnostic - Core logic works with any .NET UI framework (WPF, more coming soon)
  • Dependency Injection - Support for Microsoft.Extensions.DependencyInjection
  • Auto-Start on First Run - Optionally start the tutorial automatically when no persisted state exists

Use Cases

  • Onboarding flows - Guide new users through initial setup and configuration
  • Feature discovery - Introduce users to new or advanced features
  • Interactive help - Provide contextual guidance within complex workflows
  • Training modules - Build step-by-step training for enterprise applications

Installation

<PackageReference Include="Guidely.Core" Version="1.0.0" />

Or via .NET CLI:

dotnet add package Guidely.Core

Requirements

Framework Requirements

  • .NET 10.0 or later

NuGet Dependencies

Guidely.Core has the following dependencies (automatically installed with the package):

  • CommunityToolkit.Mvvm (8.0+) - MVVM infrastructure for ViewModels and reactive properties
  • Microsoft.Extensions.DependencyInjection.Abstractions (8.0+) - Dependency injection support

UI Framework Integration

Guidely.Core is framework-agnostic and provides only the core tutorial state machine. To display tutorials in your application, you'll need a framework-specific UI package:

  • WPF: Install Guidely.WPF for overlay controls and tutorial UI
  • MAUI/Avalonia: Coming soon

Quick Start

1. Define Your Context

Create a record inheriting from TutorialContextBase to track application state:

public record MyAppContext : TutorialContextBase
{
    public bool HasCompletedSetup { get; init; }
    public bool HasData { get; init; }
    public bool IsFeatureEnabled { get; init; }
    public string CurrentPage { get; init; } = string.Empty;
}

2. Define Triggers and Groups

public enum MyTrigger
{
    SetupCompleted,
    DataLoaded,
    FeatureEnabled
}

public enum MyGroup
{
    Introduction,
    Setup,
    Features,
    Complete
}

3. Define Step IDs

Use static string constants for compile-time validation:

public static class StepIds
{
    public const string Welcome = nameof(Welcome);
    public const string Setup = nameof(Setup);
    public const string LoadData = nameof(LoadData);
    public const string Features = nameof(Features);
    public const string Complete = nameof(Complete);
}

4. Create Tutorial Steps

Basic step:

[TutorialStep(StepIds.Welcome,
    Group = MyGroup.Introduction,
    Position = TooltipPosition.Center,
    Order = 0)]
public class WelcomeStep : ITutorialStepContent
{
    public string Title => "Welcome!";
    public string Description => "Let's get started with the tutorial.";
}

Step with user action and auto-advance:

[TutorialStep(StepIds.Setup,
    Group = MyGroup.Setup,
    Position = TooltipPosition.Right,
    RequiresUserAction = true,
    ClickThroughMode = ClickThroughMode.TargetOnly,
    Order = 0)]
[TutorialTarget("SetupButton")]
[AutoAdvanceOn(MyTrigger.SetupCompleted)]
public class SetupStep : ITutorialStepContent
{
    public string Title => "Complete Setup";
    public string Description => "Click the Setup button to configure your account.";
}

Skippable step:

[TutorialStep(StepIds.LoadData,
    Group = MyGroup.Setup,
    RequiresUserAction = true,
    ClickThroughMode = ClickThroughMode.TargetOnly,
    Order = 1)]
[TutorialTarget("LoadButton")]
[AutoAdvanceOn(MyTrigger.DataLoaded)]
public class LoadDataStep : ITutorialStepContent, ITutorialStepSkippable<MyAppContext>
{
    public string Title => "Load Data";
    public string Description => "Click Load to fetch your data.";

    // Skip if data already exists
    public bool CanSkip(MyAppContext context) => context.HasData;
    public string SkipTitle => "Data Already Loaded";
    public string SkipDescription => "You already have data. Click Next to continue.";
}

5. Configure Transitions

Create a static class with transition configuration:

public static class TutorialSetup
{
    public static void ConfigureTransitions(TransitionBuilder<MyAppContext> builder)
    {
        // Conditional transition
        builder.From(StepIds.Welcome)
            .GoToIf(StepIds.Features, ctx => ctx.HasCompletedSetup)
            .GoTo(StepIds.Setup);

        // Simple transition
        builder.From(StepIds.Setup)
            .GoTo(StepIds.LoadData);

        // Skip transition for skippable step
        builder.From(StepIds.LoadData)
            .SkipTo(StepIds.Features)      // Where Next goes when CanSkip returns true
            .GoTo(StepIds.Features);       // Normal transition after auto-advance

        // Custom back target
        builder.From(StepIds.Features)
            .BackTo(StepIds.Welcome)       // Override default back navigation
            .GoTo(StepIds.Complete);

        // Disable back navigation
        builder.From(StepIds.Complete)
            .DisableBack()
            .GoTo(StepIds.Complete);       // Stay on last step
    }
}

6. Register Services

services.AddGuidely<MyAppContext, MyTrigger, MyGroup>(builder =>
{
    builder.ScanStepsFromAssembly(typeof(WelcomeStep).Assembly);
    builder.ConfigureTransitions(TutorialSetup.ConfigureTransitions);
    builder.SetStartStep(StepIds.Welcome);
    builder.EnableAutoStart(); // Optional: auto-start tutorial on first run

    // Map groups to views for restart-from-current-view
    builder.MapGroupToView(MyGroup.Setup, ctx => ctx.CurrentPage == "Setup");
    builder.MapGroupToView(MyGroup.Features, ctx => ctx.CurrentPage == "Features");
});

7. Initialize and Update Context

Initialize the tutorial service on application startup:

// In your application startup (e.g., MainViewModel, App.xaml.cs)
await tutorialService.InitializeAsync();

// Set initial context based on current app state
tutorialService.UpdateContext(ctx => ctx with
{
    HasCompletedSetup = true,
    CurrentPage = "Dashboard"
});

Update context as the application state changes:

// Update context when app state changes
tutorialService.UpdateContext(ctx => ctx with
{
    HasData = true
});

// Or combine with trigger firing for auto-advance
tutorialService.FireTrigger(MyTrigger.SetupCompleted,
    ctx => ctx with { HasCompletedSetup = true });

Core Concepts

Tutorial Context

The context is an immutable record that captures application state. The tutorial service uses this state to evaluate conditional transitions and determine which step to show next.

public record MyAppContext : TutorialContextBase
{
    public bool IsLoggedIn { get; init; }
    public int ItemCount { get; init; }
    public string CurrentPage { get; init; } = string.Empty;
}

Update context using immutable with expressions:

tutorialService.UpdateContext(ctx => ctx with { IsLoggedIn = true });

Tutorial Steps

Steps are classes decorated with [TutorialStep] that implement ITutorialStepContent:

[TutorialStep("MyStep",
    Group = MyGroup.Features,
    Position = TooltipPosition.Bottom,
    RequiresUserAction = true,
    ClickThroughMode = ClickThroughMode.TargetOnly,
    Order = 1)]
[TutorialTarget("MyButton")]
[AutoAdvanceOn(MyTrigger.ButtonClicked)]
public class MyStep : ITutorialStepContent
{
    public string Title => "Click the Button";
    public string Description => "Click the highlighted button to continue.";
}

Type-Safe Step IDs

Use static string constants for compile-time validation:

public static class StepIds
{
    public const string Welcome = nameof(Welcome);
    public const string Setup = nameof(Setup);
    public const string Features = nameof(Features);
    public const string Complete = nameof(Complete);
}

Transitions

Configure step flow using the fluent TransitionBuilder:

// Unconditional transition
builder.From(StepIds.Welcome)
    .GoTo(StepIds.Setup);

// Conditional transition (evaluated first)
builder.From(StepIds.Welcome)
    .GoToIf(StepIds.Features, ctx => ctx.HasCompletedSetup)
    .GoTo(StepIds.Setup);  // Fallback if condition is false

// Skip transition (used when step is skippable)
builder.From(StepIds.Setup)
    .SkipTo(StepIds.Features)   // Where Next goes when CanSkip returns true
    .GoTo(StepIds.Features);    // Normal transition after auto-advance

// Explicit back target (overrides history-based back)
builder.From(StepIds.Summary)
    .BackTo(StepIds.Overview)
    .GoTo(StepIds.Complete);

// Disable back navigation for a step
builder.From(StepIds.Setup)
    .DisableBack()
    .GoTo(StepIds.Features);

Auto-Advance Triggers

Steps can auto-advance when triggers are fired:

// Simple trigger-based auto-advance
[AutoAdvanceOn(MyTrigger.ButtonClicked)]
public class ClickButtonStep : ITutorialStepContent { ... }

// Complex conditional auto-advance
[AutoAdvanceOn(MyTrigger.DataLoaded)]
public class WaitForDataStep : ITutorialStepContent, IConditionalAutoAdvance<MyAppContext, MyTrigger>
{
    public bool ShouldAutoAdvance(MyTrigger trigger, MyAppContext? previous, MyAppContext current)
        => trigger == MyTrigger.DataLoaded && current.ItemCount > 0;
}

Fire triggers from your application:

tutorialService.FireTrigger(MyTrigger.ButtonClicked);

// Or with context update in same atomic operation
tutorialService.FireTrigger(MyTrigger.DataLoaded, ctx => ctx with { ItemCount = 5 });

Skippable Steps

Steps that require user action can show alternate content and a Next button when their goal is already achieved. This is useful for tutorial reruns where some data already exists.

Implement ITutorialStepSkippable<TContext> on the step class:

[TutorialStep(StepIds.RefreshData,
    RequiresUserAction = true,
    ClickThroughMode = ClickThroughMode.TargetOnly)]
[TutorialTarget("RefreshButton")]
[AutoAdvanceOn(MyTrigger.DataRefreshed)]
public class RefreshDataStep : ITutorialStepContent, ITutorialStepSkippable<MyAppContext>
{
    public string Title => "Refresh Data";
    public string Description => "Click Refresh to load the latest data.";

    // When CanSkip returns true, the ViewModel shows SkipTitle/SkipDescription
    // and displays the Next button even though RequiresUserAction is true
    public bool CanSkip(MyAppContext context) => context.HasData;
    public string SkipTitle => "Refresh Data";
    public string SkipDescription => "You already have data loaded. Click Next to continue, or Refresh to update.";
}

Configure the skip target in transitions using SkipTo():

builder.From(StepIds.RefreshData)
    .SkipTo(StepIds.NextSection)    // Where Next goes when step is skippable
    .GoTo(StepIds.WaitForRefresh);  // Normal transition after auto-advance

When CanSkip returns true:

  • The step shows SkipTitle and SkipDescription instead of the normal content
  • The Next button appears even if RequiresUserAction = true
  • Clicking Next navigates to the SkipTo target
  • Auto-advance still works if the user performs the action
  • Back navigation skips over steps whose CanSkip returns true and stops at group boundaries

Back Navigation

Back navigation is group-bounded and skip-aware by default:

  • The back button is hidden when at the first step of a group (group boundary)
  • Steps whose CanSkip returns true are automatically skipped when going back

For custom back behavior, use BackTo() and DisableBack() in the transition builder:

// Explicit back target — overrides history-based back
builder.From(StepIds.Summary)
    .BackTo(StepIds.Overview)
    .GoTo(StepIds.Complete);

// Disable back navigation entirely for a step
builder.From(StepIds.Confirmation)
    .DisableBack()
    .GoTo(StepIds.Done);

Group-to-View Mapping

Map tutorial groups to application views/pages so that restart begins at the relevant group:

builder.MapGroupToView(MyGroup.Settings, ctx => ctx.CurrentPage == "Settings");
builder.MapGroupToView(MyGroup.Dashboard, ctx => ctx.CurrentPage == "Dashboard");

When RestartFromCurrentViewAsync() is called, the service evaluates these conditions against the current context and restarts from the matching group. If no group matches, it falls back to a full restart.

Groups

Groups organize steps into logical sections and determine step ordering:

public enum MyGroup
{
    Introduction,  // Steps with Group = 0
    Setup,         // Steps with Group = 1
    Features,      // Steps with Group = 2
    Complete       // Steps with Group = 3
}

API Reference

ITutorialService<TContext, TTrigger, TGroup>

Main service interface for controlling the tutorial.

Property Type Description
CurrentStepId string ID of the current step
CurrentStep TutorialStepMetadata? Metadata for the current step
CurrentGroup TGroup Group of the current step
IsTutorialActive bool Whether tutorial is running
IsTutorialCompleted bool Whether tutorial is finished
Context TContext Current tutorial context
CurrentStepNumber int Current step number (1-based)
TotalSteps int Total number of steps
CanGoBack bool Whether back navigation is available (within current group, considering skippable steps)
IsCurrentStepSkippable bool Whether the current step can be skipped
Method Description
InitializeAsync() Load persisted state and initialize
StartTutorial() Start or restart the tutorial
AdvanceStep() Move to the next step
GoBack() Go to the previous non-skippable step within the current group
SkipStep() Navigate using the configured skip transition
SkipTutorial() Skip the tutorial entirely
RestartTutorialAsync() Restart from the beginning
RestartFromCurrentViewAsync() Restart from the group matching the current view
FireTrigger(trigger, contextUpdate?) Fire a trigger with optional context update
UpdateContext(update) Update context without firing a trigger
RestartGroup(group) Restart from the beginning of a group

Attributes

[TutorialStep]
Property Type Default Description
Id string Required Unique step identifier
Group object? null Group enum value (e.g., MyGroup.Introduction)
Position TooltipPosition Bottom Tooltip position relative to target
RequiresUserAction bool false Hides Next button when true
ClickThroughMode ClickThroughMode None Overlay interaction mode
Order int 0 Order within group
[TutorialTarget]

Specifies which UI element(s) to highlight. Can be applied multiple times for different targets.

[TutorialTarget("SaveButton")]
[TutorialTarget("CancelButton")]

Multiple Elements with Same Target Name: When multiple UI elements share the same target name (e.g., buttons in an ItemsControl/ListView template), all matching elements will be highlighted simultaneously. This is useful for highlighting all instances of a repeated element.

WPF example:


<ItemsControl ItemsSource="{Binding Items}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Content="Refresh"
                    guidely:TutorialTarget.Element="RefreshButton" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
[AutoAdvanceOn]

Specifies triggers that can cause auto-advance. Can be applied multiple times.

[AutoAdvanceOn(MyTrigger.ItemSaved)]
[AutoAdvanceOn(MyTrigger.ItemDeleted)]

Enums

TooltipPosition
public enum TooltipPosition
{
    Top,
    Bottom,
    Left,
    Right,
    Center
}
ClickThroughMode
public enum ClickThroughMode
{
    None,       // Overlay blocks all interaction
    TargetOnly, // Only target element(s) can be clicked
    All         // All elements can be clicked through
}

ITutorialPersistence

Implement this interface to persist tutorial state:

public class MyPersistence : ITutorialPersistence
{
    public async Task<TutorialState?> LoadStateAsync(CancellationToken ct = default)
    {
        // Load from database, file, etc.
    }

    public async Task SaveStateAsync(TutorialState state, CancellationToken ct = default)
    {
        // Save to database, file, etc.
    }
}

Register before AddGuidely:

services.AddGuidelyPersistence<MyPersistence>();
services.AddGuidely<...>(...);

Dependency Injection

AddGuidely<TContext, TTrigger, TGroup>

Registers the tutorial service, configuration, and view model.

services.AddGuidely<MyAppContext, MyTrigger, MyGroup>(builder =>
{
    builder.ScanStepsFromAssembly(typeof(WelcomeStep).Assembly);
    builder.ConfigureTransitions(MySetup.ConfigureTransitions);
    builder.SetStartStep(StepIds.Welcome);
    builder.EnableAutoStart(); // Optional: auto-start on first run
    builder.UseStepFactory((sp, type) => sp.GetRequiredService(type)); // Optional
});
Builder Methods
Method Description
ScanStepsFromAssembly(assembly) Scans an assembly for step classes marked with [TutorialStep]
ConfigureTransitions(action) Configures transitions between steps using TransitionBuilder
SetStartStep(stepId) Sets the starting step ID
EnableAutoStart() Enables automatic tutorial start on first run (no persisted state)
UseStepFactory(factory) Sets a custom factory for creating step instances
MapGroupToView(group, condition) Maps a group to a view condition for restart-from-current-view

AddGuidelyPersistence<TPersistence>

Registers a custom persistence implementation:

services.AddGuidelyPersistence<MyPersistence>();

// Or with factory
services.AddGuidelyPersistence(sp => new MyPersistence(sp.GetRequiredService<IDb>()));

Complete Example

// Context
public record OnboardingContext : TutorialContextBase
{
    public bool HasProfile { get; init; }
    public bool HasConnectedAccount { get; init; }
}

// Triggers
public enum OnboardingTrigger
{
    ProfileCreated,
    AccountConnected
}

// Groups
public enum OnboardingGroup
{
    Welcome,
    Profile,
    Account,
    Done
}

// Step IDs
public static class StepIds
{
    public const string Welcome = nameof(Welcome);
    public const string CreateProfile = nameof(CreateProfile);
    public const string ConnectAccount = nameof(ConnectAccount);
    public const string AllDone = nameof(AllDone);
}

// Steps
[TutorialStep(StepIds.Welcome, Group = OnboardingGroup.Welcome, Position = TooltipPosition.Center)]
public class WelcomeStep : ITutorialStepContent
{
    public string Title => "Welcome!";
    public string Description => "Let's set up your account.";
}

[TutorialStep(StepIds.CreateProfile,
    Group = OnboardingGroup.Profile,
    Position = TooltipPosition.Right,
    RequiresUserAction = true,
    ClickThroughMode = ClickThroughMode.TargetOnly)]
[TutorialTarget("ProfileButton")]
[AutoAdvanceOn(OnboardingTrigger.ProfileCreated)]
public class CreateProfileStep : ITutorialStepContent, IConditionalAutoAdvance<OnboardingContext, OnboardingTrigger>
{
    public string Title => "Create Your Profile";
    public string Description => "Click to create your profile.";

    public bool ShouldAutoAdvance(OnboardingTrigger trigger, OnboardingContext? prev, OnboardingContext curr)
        => trigger == OnboardingTrigger.ProfileCreated && curr.HasProfile;
}

// Transitions
public static class OnboardingSetup
{
    public static void ConfigureTransitions(TransitionBuilder<OnboardingContext> builder)
    {
        builder.From(StepIds.Welcome)
            .GoToIf(StepIds.ConnectAccount, ctx => ctx.HasProfile)
            .GoTo(StepIds.CreateProfile);

        builder.From(StepIds.CreateProfile)
            .GoTo(StepIds.ConnectAccount);

        builder.From(StepIds.ConnectAccount)
            .GoTo(StepIds.AllDone);
    }
}

// Registration
services.AddGuidely<OnboardingContext, OnboardingTrigger, OnboardingGroup>(builder =>
{
    builder.ScanStepsFromAssembly(typeof(WelcomeStep).Assembly);
    builder.ConfigureTransitions(OnboardingSetup.ConfigureTransitions);
    builder.SetStartStep(StepIds.Welcome);
    builder.EnableAutoStart(); // Start tutorial automatically on first run
});

License

Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE for details.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Guidely.Core:

Package Downloads
Guidely.WPF

WPF controls and overlays for Guidely interactive tutorials

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 113 2/11/2026