Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.presentum.dev/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Guards are where your presentation business logic lives. This guide shows you how to build guards from simple to production-level complexity.

Basic guard

Start with a simple guard that sets the highest priority item as active:
final class FeatureSchedulingGuard
    extends PresentumGuard<FeatureItem, AppSurface, AppVariant> {
  FeatureSchedulingGuard({
    required this.eligibilityResolver,
    super.refresh,
  });

  final EligibilityResolver<FeatureItem> eligibilityResolver;

  @override
  Future<PresentumState<FeatureItem, AppSurface, AppVariant>> call(
    PresentumStorage<AppSurface, AppVariant> storage,
    List<PresentumHistoryEntry<FeatureItem, AppSurface, AppVariant>> history,
    PresentumState$Mutable<FeatureItem, AppSurface, AppVariant> state,
    List<FeatureItem> candidates,
    Map<String, Object?> context,
  ) async {
    // 1) Filter: if feature is gone, disabled, ineligible, or dismissed,
    // exclude it from UI.
    final filtered = <FeatureItem>[];

    for (final item in candidates) {
      // Check eligibility (time ranges, segments, etc.)
      final isEligible = await eligibilityResolver.isEligible(item, context);
      if (!isEligible) continue;

      // Check if feature is dismissed
      final dismissedAt = await storage.getDismissedAt(
        item.id,
        surface: item.surface,
        variant: item.variant,
      );
      if (dismissedAt != null) continue;

      filtered.add(item);
    }

    // 3) Project candidates -> slots (active + queue)
    // This is what makes Settings rows and UI banners actually appear.
    state.clearAll();

    final bySurface = <AppSurface, List<FeatureItem>>{};
    for (final item in filtered) {
      (bySurface[item.surface] ??= <FeatureItem>[]).add(item);
    }

    for (final entry in bySurface.entries) {
      final surface = entry.key;
      final items = entry.value;

      int stageOf(FeatureItem i) => i.stage ?? 0;

        items.sort((a, b) {
          final stageCmp = stageOf(a).compareTo(stageOf(b));
          if (stageCmp != 0) return stageCmp;
          return b.priority.compareTo(a.priority);
        });

      state.addAll(surface, items);
    }

    return state;
  }
}

Production guard examples

See real implementations:

See all production guards

Complete guard implementations from a live app

See all reusable guards

Generic guards to sync state, remove ineligible candidates and simple eligibility scheduling