Table of Contents

Feature Warnings

Feature warnings provide a mechanism to notify users of configuration issues and automatically disable affected tools until the problem is resolved. Warning state is runtime/session-only and is never persisted to disk.


Overview

Feature warnings serve two purposes:

  1. User Notification: Display prominent warnings in the Settings window when configuration issues exist
  2. Tool Protection: Automatically disable ribbon buttons for tools that depend on valid configuration

Warnings are declarative (defined in manifest.yml) but activated programmatically based on validation logic. When a warning is active:

  • The warning card appears in the Settings window
  • Associated ribbon buttons become disabled
  • The Settings button icon shows a warning indicator
  • The DB Tools tab gets a highlight marker

Source: src/DBTools.Core/Settings/DbtSettingsWarningDefinition.cs:9-20


Warning Lifecycle

1. DEFINITION (manifest.yml)
   Warning metadata declared: id, title, message, disableTools
           |
           v
2. REGISTRATION (ToolModule.RegisterSettingsPacks)
   DbtSettingsWarningDefinition created with validation logic
           |
           v
3. VALIDATION (Settings window load / manual trigger)
   Pack context validates settings and computes warning state
           |
           v
4. ACTIVATION (IDbtSettingsWarningService.SetWarningAsync)
   Warning state stored in-session, ribbon buttons disabled
           |
           v
5. DISPLAY (SettingsWarningCardView)
   Warning card shown with Clear button
           |
           v
6. CLEARING (User clicks Clear / conditions change)
   Warning state cleared, ribbon buttons re-enabled

Defining Warnings in manifest.yml

Warnings are declared within settingsPacks in your tool's manifest.yml:

Complete Schema

tool:
  settingsPacks:
    - key: structural.foundation_tags          # Unique pack identifier
      title: "Combined Foundation Tags"         # Display title in Settings
      warnings:
        - id: core.structural.combined_tags    # Unique warning identifier
          title: "Combined Foundation Tags Disabled"  # Warning card title
          message: "Combined foundation tag updates are disabled due to a warning. Clear the warning to re-enable."
          disableTools:                         # Tools to disable when active
            - DBTools.UpdateCombinedFoundationTags
            - DBTools.MoveCombinedFoundationTags
            - DBTools.OrganizeFoundationTypes

Source: src/Tools/Structural/FoundationTags/manifest.yml:8-18

Warning Properties Reference

Property Required Description
id Yes Globally unique warning identifier (e.g., core.structural.combined_tags)
title Yes Title shown in the warning card header
message Yes Detailed message explaining the issue and resolution
disableTools No List of ribbon tool internalName values to disable

ID Naming Convention

Use a hierarchical naming scheme:

  • core.{domain}.{feature} - For core/shared functionality
  • tools.{toolname}.{warning} - For tool-specific warnings

Examples:

  • core.library.invalid - Library paths are invalid
  • core.structural.elevation_tags - Elevation tag alignment warning
  • core.structural.joist_girder - Joist girder weight warning

Warning Properties in Code

The DbtToolWarningManifest class represents the YAML definition:

public sealed class DbtToolWarningManifest
{
    public string Id { get; set; } = string.Empty;
    public string Title { get; set; } = string.Empty;
    public string Message { get; set; } = string.Empty;
    public List<string>? DisableTools { get; set; }
}

Source: src/DBTools.Core/Tools/DbtToolManifest.cs:44-50


Registering Warning Definitions

Warnings must be registered in your ToolModule.RegisterSettingsPacks() method. This connects the declarative manifest to the runtime validation logic.

Complete Registration Pattern

public override void RegisterSettingsPacks(IServiceCollection services, DbtToolManifest manifest)
{
    Guard.NotNull(services, nameof(services));
    Guard.NotNull(manifest, nameof(manifest));
    
    var configSection = manifest.GetRequiredConfigSection();
    var pack = manifest.GetSingleSettingsPack();
    var warning = pack.GetSingleWarning(manifest.Id);
    var panel = DbtToolPanelResolver.GetSettingsPanelName(manifest.Id);
    
    services.AddSingleton<IDbtSettingsPackDefinition>(_ =>
    {
        var warningDefinitions = new[]
        {
            new DbtSettingsWarningDefinition<MyToolSettings, MySettingsPackContext>(
                warning.Id,                           // From manifest
                pack.Key,                             // Pack key
                warning.Title,                        // From manifest
                warning.Message,                      // From manifest
                // Context flag getter - read warning state from context
                context => context.HasWarning,
                // Context flag setter - write warning state to context
                (context, active) => context.HasWarning = active,
                // Tools to disable (from manifest)
                warning.DisableTools?.ToArray() ?? Array.Empty<string>())
        };

        return new DbtSettingsPackDefinition<MyToolSettings, MySettingsPackContext>(
            pack.Key,
            pack.Title,
            panel,
            () => new MySettingsPackContext(),
            ctx => new MySettingsPackView { DataContext = ctx },
            () => new MyToolSettings(),
            settings => settings.Get<MyToolSettings>(),
            (settings, options, ct) => settings.SaveAsync(configSection, options, ct),
            warningDefinitions);  // Pass warning definitions to pack
    });
}

Source: src/Tools/Structural/FoundationTags/FoundationTagsToolModule.cs:36-81

DbtSettingsWarningDefinition Constructor Parameters

Parameter Type Description
id string Warning identifier from manifest
packKey string Parent settings pack key
title string Display title
message string Display message
contextFlagGetter Func<TContext, bool> Get warning flag from context
contextFlagSetter Action<TContext, bool> Set warning flag on context
toolInternalNamesToDisable IReadOnlyCollection<string>? Tools to disable

Source: src/DBTools.Core/Settings/DbtSettingsWarningDefinition.cs:33-57


Settings Model Requirements

Warning state is session-only. Settings models should persist user preferences (for example AutoUpdateEnabled) but should not persist warning flags.

using DBTools.Core.Settings.Models;

namespace DBTools.MyTool.Settings;

public sealed class MyToolSettings : IAutoUpdateSettings
{
    public bool AutoUpdateEnabled { get; set; }
    
    // Other settings...
}

The IAutoUpdateSettings interface requires only persisted auto-update preference:

public interface IAutoUpdateSettings
{
    bool AutoUpdateEnabled { get; set; }
}

Source: src/DBTools.Core/Settings/Models/IAutoUpdateSettings.cs:3-7


Triggering Warnings

Warnings are triggered through the IDbtSettingsWarningService:

Service Interface

public interface IDbtSettingsWarningService
{
    IReadOnlyCollection<IDbtSettingsWarningDefinition> Definitions { get; }
    Task<bool> IsWarningActiveAsync(string warningId, CancellationToken ct = default);
    Task SetWarningAsync(string warningId, bool active, object? context = null, CancellationToken ct = default);
    Task PublishWarningChangesAsync(CancellationToken ct = default);
    Task PublishWarningStateAsync(CancellationToken ct = default);
}

Source: src/DBTools.Core/Settings/DbtSettingsWarningService.cs:11-18

Setting a Warning Active

// In your validation logic
var warningService = serviceProvider.GetRequiredService<IDbtSettingsWarningService>();
await warningService.SetWarningAsync("core.my_tool.warning", true, packContext);

Clearing a Warning

await warningService.SetWarningAsync("core.my_tool.warning", false, packContext);

Validation in Settings Pack Context

Typically, warnings are validated when the settings pack loads or saves. Implement validation in your context's ValidateAsync method:

public async Task<DbtSettingsValidationResult> ValidateAsync(CancellationToken ct)
{
    var errors = new List<string>();
    
    // Validate configuration
    if (string.IsNullOrWhiteSpace(LibraryPath) || !Directory.Exists(LibraryPath))
    {
        errors.Add($"Library path '{LibraryPath}' does not exist.");
        HasWarnings = true;  // Set context warning flag
    }
    else
    {
        HasWarnings = false;
    }
    
    return errors.Count > 0
        ? DbtSettingsValidationResult.Failed(errors)
        : DbtSettingsValidationResult.Success();
}

Source: src/Tools/Common/TDV/Settings/LibrarySettingsPackContext.cs:137-170


Warning UI in Settings Window

When warnings are active, they appear as prominent cards in the Settings window:

SettingsWarningCardView

<Border Style="{DynamicResource WarningBar}"
        Visibility="{Binding IsActive, Converter={StaticResource Converter.BoolToVisibility}}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <StackPanel>
            <TextBlock FontWeight="SemiBold"
                       Foreground="{DynamicResource {x:Static theme:BrushKeys.Warning}}"
                       Text="{Binding Title}" />
            <TextBlock Margin="{DynamicResource TGap6}"
                       Style="{DynamicResource DescriptionText}"
                       Text="{Binding Message}" />
        </StackPanel>
        <Button Grid.Column="1"
                MinWidth="70"
                Command="{Binding ClearCommand}"
                Content="Clear"
                Style="{DynamicResource SubtleButton}" />
    </Grid>
</Border>

Source: src/DBTools.Core/Settings/Views/SettingsWarningCardView.xaml:14-37

Warning ViewModel

The DbtSettingsWarningViewModel provides the binding context:

public sealed partial class DbtSettingsWarningViewModel : ObservableObject, IDisposable
{
    public string WarningId => _definition.Id;
    public string Title => _definition.Title;
    public string Message => _definition.Message;
    public bool IsActive => _definition.GetIsActive(_context);
    public IAsyncRelayCommand ClearCommand { get; }

    private async Task ClearAsync()
    {
        await _warnings.SetWarningAsync(_definition.Id, false, _context);
        OnPropertyChanged(nameof(IsActive));
    }
}

Source: src/DBTools.Core/Settings/ViewModels/DbtSettingsWarningViewModel.cs:10-57


disableTools Mechanism

When a warning is active, the specified ribbon tools are automatically disabled.

Event Flow

  1. Warning Activated: IDbtSettingsWarningService.SetWarningAsync() is called
  2. Event Published: ISettingsChangePublisher.PublishWarningChanged() fires
  3. Listener Receives: RibbonSettingsListener.OnWarningChanged() handles event
  4. Buttons Disabled: RibbonRegistry.SetToolsEnabled() disables buttons
// RibbonSettingsListener.cs
private void OnWarningChanged(object? sender, SettingsWarningChangedEventArgs e)
{
    RunOnUiThread(
        () => RibbonRegistry.SetToolsEnabled(e.ToolInternalNamesToDisable, !e.IsActive),
        "Failed to apply ribbon warning change.");
}

Source: src/DBTools.App/Features/Ribbon/RibbonSettingsListener.cs:53-58

RibbonRegistry Implementation

public static void SetToolsEnabled(IEnumerable<string> internalNames, bool enabled)
{
    foreach (var name in internalNames)
    {
        try { SetToolEnabled(name, enabled); }
        catch (Exception ex) { Logger?.LogWarning(ex, "Failed to set tool '{InternalName}' enabled state.", name); }
    }
}

public static void SetToolEnabled(string internalName, bool enabled)
{
    if (!_toolButtons.TryGetValue(internalName, out var button))
    {
        Logger?.LogWarning("Tool button '{InternalName}' not found in registry.", internalName);
        return;
    }
    button.Enabled = enabled;
}

Source: src/DBTools.App/Features/Ribbon/RibbonRegistry.cs:31-52

Visual Indicators

When warnings exist:

  1. Settings Button Icon: Changes to show warning indicator
  2. Tab Highlight: DB Tools tab shows a "New" highlight
private void OnWarningsChanged(object? sender, SettingsWarningsChangedEventArgs e)
{
    RunOnUiThread(
        () =>
        {
            RibbonRegistry.UpdateSettingsIcon(e.HasWarnings);
            RibbonRegistry.UpdateTabHighlight(e.HasWarnings);
        },
        "Failed to apply ribbon warning updates.");
}

Source: src/DBTools.App/Features/Ribbon/RibbonSettingsListener.cs:42-51


Conditional Warnings

Warnings can be conditional based on settings or environment. Implement the condition in your validation logic:

Based on Settings Value

public async Task<DbtSettingsValidationResult> ValidateAsync(CancellationToken ct)
{
    // Only validate if feature is enabled
    if (!AutoUpdateEnabled)
    {
        HasWarning = false;
        return DbtSettingsValidationResult.Success();
    }
    
    // Validate when enabled
    if (!IsConfigurationValid())
    {
        HasWarning = true;
        return DbtSettingsValidationResult.Failed("Configuration is invalid.");
    }
    
    HasWarning = false;
    return DbtSettingsValidationResult.Success();
}

Based on Environment

public async Task<DbtSettingsValidationResult> ValidateAsync(CancellationToken ct)
{
    var errors = new List<string>();
    
    foreach (var path in LibraryPaths)
    {
        if (!Directory.Exists(path))
        {
            errors.Add($"Path '{path}' does not exist.");
        }
    }
    
    HasWarnings = errors.Count > 0;
    return HasWarnings 
        ? DbtSettingsValidationResult.Failed(errors)
        : DbtSettingsValidationResult.Success();
}

Source: src/Tools/Common/TDV/Settings/LibrarySettingsPackContext.cs:137-170


Auto-Update Task Integration

For tools with auto-update functionality (triggered on view activation), the ViewActivatedTaskBase checks warning state before execution:

public async Task<bool> ShouldRunAsync(UIApplication uiapp, ViewActivatedEventArgs args, CancellationToken ct)
{
    var settings = GetSettings(_settingsProvider) ?? new TSettings();
    
    if (!settings.AutoUpdateEnabled)
    {
        LogSkip("auto-update disabled");
        return false;
    }

    if (settings.HasWarning)
    {
        LogSkip("warning active");
        return false;
    }

    return await ShouldRunInternalAsync(uiapp, args, settings, ct).ConfigureAwait(false);
}

Source: src/DBTools.Core/Tools/ViewActivatedTaskBase.cs:46-65

This ensures auto-update tasks don't run when warnings are active, protecting against potential failures.


Clearing Warnings

Warnings can be cleared in several ways:

1. User Clicks Clear Button

The Settings window warning card has a "Clear" button that calls:

private async Task ClearAsync()
{
    await _warnings.SetWarningAsync(_definition.Id, false, _context);
    OnPropertyChanged(nameof(IsActive));
}

2. Programmatically When Conditions Change

// After user fixes configuration
if (IsConfigurationNowValid())
{
    await warningService.SetWarningAsync("core.my_tool.warning", false, context);
}

3. Through Settings Pack Context Reset

public void ResetToDefaults()
{
    AutoUpdateEnabled = true;
    HasWarning = false;  // Clear warning flag
    // Reset other settings...
}

Source: src/Tools/Structural/FoundationTags/Settings/FoundationTagsSettingsPackContext.cs:206-212


Real Examples

Example 1: Foundation Tags Warning

Disables multiple related tools when foundation tag configuration has issues:

# manifest.yml
settingsPacks:
  - key: structural.foundation_tags
    title: "Combined Foundation Tags"
    warnings:
      - id: core.structural.combined_tags
        title: "Combined Foundation Tags Disabled"
        message: "Combined foundation tag updates are disabled due to a warning. Clear the warning to re-enable."
        disableTools:
          - DBTools.UpdateCombinedFoundationTags
          - DBTools.MoveCombinedFoundationTags
          - DBTools.OrganizeFoundationTypes

Source: src/Tools/Structural/FoundationTags/manifest.yml:8-18

Example 2: Library Paths Warning

Disables import/export when library paths are invalid:

# manifest.yml
settingsPacks:
  - key: core.library
    title: "Library Files"
    warnings:
      - id: core.library.invalid
        title: "Library Paths Invalid"
        message: "One or more library file paths are invalid. Fix the paths and clear the warning to re-enable import/export."
        disableTools:
          - DBTools.ExportToLibrary
          - DBTools.ImportFromLibrary

Source: src/Tools/Common/TDV/manifest.yml:14-23

Example 3: Simple Single-Tool Warning

# manifest.yml
settingsPacks:
  - key: structural.joist_girder_weight
    title: "Joist Girder Weights"
    warnings:
      - id: core.structural.joist_girder
        title: "Joist Girder Weights Disabled"
        message: "Joist girder weight updates are disabled due to a warning. Clear the warning to re-enable."
        disableTools:
          - DBTools.UpdateJoistGirderWeights

Source: src/Tools/Structural/JoistGirderWeight/manifest.yml:8-16


Warning Event Args

The warning system uses specific event args for state changes:

SettingsWarningChangedEventArgs

Fired when a specific warning's state changes:

public sealed class SettingsWarningChangedEventArgs : EventArgs
{
    public string WarningId { get; }
    public string PackKey { get; }
    public bool IsActive { get; }
    public IReadOnlyCollection<string> ToolInternalNamesToDisable { get; }
}

Source: src/DBTools.Core/Settings/Contracts/ISettingsChangePublisher.cs:25-43

SettingsWarningsChangedEventArgs

Fired when the overall warning state changes (any warnings -> no warnings, or vice versa):

public sealed class SettingsWarningsChangedEventArgs : EventArgs
{
    public bool HasWarnings { get; }
}

Source: src/DBTools.Core/Settings/Contracts/ISettingsChangePublisher.cs:15-23


Best Practices

1. Clear Warning Messages

Write messages that explain:

  • What is wrong
  • Why it matters
  • How to fix it
# Good
message: "One or more library file paths are invalid. Fix the paths and clear the warning to re-enable import/export."

# Bad
message: "Warning active."

2. Disable Only Affected Tools

Only list tools that actually depend on the valid configuration:

# Good - only disables tools that use foundation tags
disableTools:
  - DBTools.UpdateCombinedFoundationTags
  - DBTools.MoveCombinedFoundationTags

# Bad - disables unrelated tools
disableTools:
  - DBTools.UpdateCombinedFoundationTags
  - DBTools.SomeUnrelatedTool

3. Auto-Disable Auto-Update

When setting a warning active, also disable auto-update to prevent background failures:

// Context flag setter (for auto-update packs)
(context, active) =>
{
    context.HasWarning = active;
    if (active) context.AutoUpdateEnabled = false;  // Optional: keep background hooks quiet
}

4. Test Warning Scenarios

Ensure your tool handles:

  • Warning active on startup (validate-aware packs are evaluated during addin startup before the initial warning publish)
  • Warning cleared mid-session
  • Multiple warnings in same pack
  • Runtime exceptions in hooks (banner + session-disable; do not activate feature warnings)
  • Validation exceptions in validate-aware packs (exceptions are runtime errors; log and do not activate feature warnings)

Source: src/DBTools.App/Addin/AddinEntry.cs:324 Source: src/DBTools.Core/Settings/Features/DbtSettingsStartupWarningEvaluator.cs:36


Cross-References