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:
- User Notification: Display prominent warnings in the Settings window when configuration issues exist
- 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 functionalitytools.{toolname}.{warning}- For tool-specific warnings
Examples:
core.library.invalid- Library paths are invalidcore.structural.elevation_tags- Elevation tag alignment warningcore.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
- Warning Activated:
IDbtSettingsWarningService.SetWarningAsync()is called - Event Published:
ISettingsChangePublisher.PublishWarningChanged()fires - Listener Receives:
RibbonSettingsListener.OnWarningChanged()handles event - 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:
- Settings Button Icon: Changes to show warning indicator
- 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:324Source:src/DBTools.Core/Settings/Features/DbtSettingsStartupWarningEvaluator.cs:36
Cross-References
- Creating New Tools - Tool module registration
- Settings Packs - Settings pack configuration
- Modularity - Tool module architecture