Skip to content

Conversation

@vaceslav
Copy link
Contributor

@vaceslav vaceslav commented Jan 7, 2026

Summary

Implements processing warnings system for Templify (closes #69).

This feature collects non-fatal warnings during template processing and provides the ability to generate a Word document report of all warnings.

Key Changes

Core Library (TriasDev.Templify)

  • Add ProcessingWarning class with factory methods for different warning types
  • Add IWarningCollector interface and WarningCollector implementation
  • Add WarningReportGenerator that uses Templify itself to generate reports
  • Extend ProcessingResult with Warnings, HasWarnings, GetWarningReport(), and GetWarningReportBytes()
  • Update LoopVisitor to collect warnings for missing/null collections
  • Update PlaceholderVisitor to collect warnings for missing variables and failed expressions
  • Update ConditionalVisitor to accept IWarningCollector

Warning Types

  • MissingVariable - placeholder variable not found in data
  • MissingLoopCollection - loop collection not found in data
  • NullLoopCollection - loop collection found but value is null
  • ExpressionFailed - expression evaluation failed

GUI (TriasDev.Templify.Gui)

  • Add "Generate Warning Report" button (enabled when warnings exist)
  • Display warning summary after processing
  • Auto-open generated report

Tests

  • Add comprehensive integration tests for all warning types
  • Add tests for warning report generation
  • Update existing visitor tests for new constructor signatures

Test plan

  • All 953 tests pass
  • dotnet format --verify-no-changes passes
  • Missing variables generate warnings
  • Missing loop collections generate warnings
  • Null loop collections generate warnings
  • Failed expressions generate warnings
  • Warning report generates valid .docx
  • GUI button works correctly

Add comprehensive warning collection during template processing:
- ProcessingWarning type with rich context (type, message, variable name, context)
- IWarningCollector interface for collecting warnings during visitor traversal
- Warnings for missing variables, missing loop collections, null loop collections
- ProcessingResult.Warnings property and HasWarnings helper

Add warning report generation:
- GetWarningReport() returns MemoryStream with formatted .docx report
- GetWarningReportBytes() returns byte array for convenience
- Uses embedded template processed by Templify itself
- WarningReportTemplateGenerator creates the embedded template

Integrate into GUI:
- Display warnings summary after template processing
- "Generate Warning Report" button to save detailed report
- Auto-opens generated report in default application

Normalize loop behavior:
- Null collections now treated same as missing (silent removal)
- Previously null collections threw exception while missing were silently removed
- Add warnings when expression evaluation fails in PlaceholderVisitor
  (ArgumentException, InvalidOperationException, InvalidCastException,
  and parse failures now generate ExpressionFailed warnings)
- Add .vscode/ to .gitignore and remove settings.json from tracking
@vaceslav vaceslav changed the title fix: add ExpressionFailed warnings and ignore .vscode directory feat: add processing warnings with report generation Jan 7, 2026
@vaceslav vaceslav requested a review from Copilot January 7, 2026 16:19
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive processing warnings system for Templify that collects non-fatal warnings during template processing and enables generation of Word document reports containing all warnings. While the implementation addresses issue #69 and provides valuable diagnostic capabilities, there are critical bugs related to the ExpressionFailed warning type that must be fixed before merging.

Key Changes:

  • Added warning collection infrastructure with ProcessingWarning, IWarningCollector, and warning report generation
  • Extended visitors to collect warnings for missing variables, null/missing collections, and failed expressions
  • Added GUI support with warning summary display and report generation button

Reviewed changes

Copilot reviewed 18 out of 22 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
TriasDev.Templify/Core/ProcessingWarning.cs Defines warning types and factory methods for creating warnings
TriasDev.Templify/Core/IWarningCollector.cs Interface and implementation for collecting warnings during processing
TriasDev.Templify/Core/WarningReportGenerator.cs [BUG] Generates Word reports from warnings but missing ExpressionFailed handling
TriasDev.Templify/Core/ProcessingResult.cs Adds Warnings property and report generation methods to ProcessingResult
TriasDev.Templify/Visitors/PlaceholderVisitor.cs Collects warnings for missing variables and failed expressions
TriasDev.Templify/Visitors/LoopVisitor.cs Collects warnings for missing and null collections, removes unused ValueResolver
TriasDev.Templify/Visitors/ConditionalVisitor.cs Updated constructor to accept IWarningCollector
TriasDev.Templify/Core/DocumentTemplateProcessor.cs Integrates warning collector and passes warnings to result
TriasDev.Templify/Resources/WarningReportTemplate.docx Embedded template for rendering warning reports
TriasDev.Templify/TriasDev.Templify.csproj Adds embedded resource for warning report template
TriasDev.Templify.DocumentGenerator/Generators/WarningReportTemplateGenerator.cs [BUG] Generates template but missing ExpressionFailed section
TriasDev.Templify.DocumentGenerator/Program.cs Registers warning report template generator
TriasDev.Templify.Tests/Integration/ProcessingWarningsIntegrationTests.cs [MISSING] Integration tests but lacks ExpressionFailed coverage
TriasDev.Templify.Tests/Visitors/*.cs Updates all visitor tests with new constructor signatures
TriasDev.Templify.Gui/Views/MainWindow.axaml Adds "Generate Warning Report" button to UI
TriasDev.Templify.Gui/ViewModels/MainWindowViewModel.cs Implements warning display and report generation logic
examples/templates/warning-report-template.docx Example warning report template
examples/outputs/warning-report-output.docx Example generated warning report output
.gitignore Adds VS Code settings exclusion
Comments suppressed due to low confidence (1)

TriasDev.Templify.Gui/ViewModels/MainWindowViewModel.cs:230

  • The LastProcessingResult is stored regardless of whether processing succeeded or failed. When processing fails (result.Success is false), the result may not have warnings or may have an error state. The CanGenerateWarningReport method checks for result.Success, but this creates an inconsistency where a failed result is stored but the warning report button is disabled. Consider only storing LastProcessingResult when result.Success is true, or clear it when processing fails, to avoid confusion and maintain consistency.
            // Store for warning report generation
            LastProcessingResult = result;

            if (result.Success)
            {
                Results.Add("✓ Template processed successfully!");
                Results.Add($"✓ Made {result.Processing.ReplacementCount} replacements");
                Results.Add($"✓ Output saved to: {result.OutputPath}");

                if (result.Processing.HasWarnings)
                {
                    Results.Add($"⚠ {result.Processing.Warnings.Count} processing warnings:");
                    foreach (ProcessingWarning warning in result.Processing.Warnings.Take(5))
                    {
                        Results.Add($"  - {warning.Type}: {warning.VariableName}");
                    }
                    if (result.Processing.Warnings.Count > 5)
                    {
                        Results.Add($"  ... and {result.Processing.Warnings.Count - 5} more");
                    }
                    Results.Add("  (Use 'Generate Warning Report' for full details)");
                }

                StatusMessage = "Processing complete";
            }
            else
            {
                Results.Add($"✗ Processing failed: {result.Processing.ErrorMessage}");
                StatusMessage = "Processing failed";
            }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 81 to 92
switch (warning.Type)
{
case ProcessingWarningType.MissingVariable:
missingVariables.Add(warningData);
break;
case ProcessingWarningType.MissingLoopCollection:
missingCollections.Add(warningData);
break;
case ProcessingWarningType.NullLoopCollection:
nullCollections.Add(warningData);
break;
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The switch statement does not handle ProcessingWarningType.ExpressionFailed warnings. When ExpressionFailed warnings are collected (e.g., from failed placeholder expressions in PlaceholderVisitor lines 90, 96, 102, 109), they will be silently dropped and not included in the warning report. Add a case for ExpressionFailed warnings, similar to how the other warning types are handled.

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +93
AddParagraph(body, "{{/if}}");

// Footer
AddEmptyParagraph(body);
AddParagraph(body, "End of Warning Report");

Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning report template does not include a section for ExpressionFailed warnings. While the template includes sections for missing variables, missing collections, and null collections, it's missing a corresponding section for failed expressions. This means ExpressionFailed warnings won't be displayed in the report even if the generator is fixed to include them in the data.

Copilot uses AI. Check for mistakes.
Comment on lines 100 to 128
public override Dictionary<string, object> GetSampleData()
{
// Sample data for testing the template
return new Dictionary<string, object>
{
["GeneratedAt"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
["TotalWarnings"] = 5,
["MissingVariableCount"] = 2,
["MissingCollectionCount"] = 2,
["NullCollectionCount"] = 1,
["HasMissingVariables"] = true,
["HasMissingCollections"] = true,
["HasNullCollections"] = true,
["MissingVariables"] = new List<Dictionary<string, object>>
{
new() { ["VariableName"] = "CustomerName", ["Context"] = "placeholder" },
new() { ["VariableName"] = "Customer.Email", ["Context"] = "placeholder" }
},
["MissingCollections"] = new List<Dictionary<string, object>>
{
new() { ["VariableName"] = "OrderItems", ["Context"] = "loop: OrderItems" },
new() { ["VariableName"] = "Categories", ["Context"] = "loop: Categories" }
},
["NullCollections"] = new List<Dictionary<string, object>>
{
new() { ["VariableName"] = "Products", ["Context"] = "loop: Products" }
}
};
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sample data for the warning report template does not include test data for ExpressionFailed warnings. While all other warning types (MissingVariable, MissingLoopCollection, NullLoopCollection) have sample data, ExpressionFailed is missing. This means the template cannot be properly tested for this warning type.

Copilot uses AI. Check for mistakes.
Comment on lines 16 to 402
public sealed class ProcessingWarningsIntegrationTests
{
[Fact]
public void ProcessTemplate_MissingVariable_CollectsWarning()
{
// Arrange
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("Hello {{MissingName}}!");

Dictionary<string, object> data = new Dictionary<string, object>();

using MemoryStream templateStream = builder.ToStream();
using MemoryStream outputStream = new MemoryStream();

// Act
DocumentTemplateProcessor processor = new DocumentTemplateProcessor();
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Assert
Assert.True(result.IsSuccess);
Assert.True(result.HasWarnings);
Assert.Single(result.Warnings);

ProcessingWarning warning = result.Warnings[0];
Assert.Equal(ProcessingWarningType.MissingVariable, warning.Type);
Assert.Equal("MissingName", warning.VariableName);
Assert.Contains("MissingName", warning.Message);
}

[Fact]
public void ProcessTemplate_MultipleMissingVariables_CollectsAllWarnings()
{
// Arrange
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("{{Missing1}} and {{Missing2}} and {{Missing3}}");

Dictionary<string, object> data = new Dictionary<string, object>();

using MemoryStream templateStream = builder.ToStream();
using MemoryStream outputStream = new MemoryStream();

// Act
DocumentTemplateProcessor processor = new DocumentTemplateProcessor();
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Assert
Assert.True(result.IsSuccess);
Assert.Equal(3, result.Warnings.Count);
Assert.All(result.Warnings, w => Assert.Equal(ProcessingWarningType.MissingVariable, w.Type));
}

[Fact]
public void ProcessTemplate_MissingLoopCollection_CollectsWarning()
{
// Arrange
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("{{#foreach MissingItems}}");
builder.AddParagraph("Item: {{.}}");
builder.AddParagraph("{{/foreach}}");

Dictionary<string, object> data = new Dictionary<string, object>();

using MemoryStream templateStream = builder.ToStream();
using MemoryStream outputStream = new MemoryStream();

// Act
DocumentTemplateProcessor processor = new DocumentTemplateProcessor();
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Assert
Assert.True(result.IsSuccess);
Assert.True(result.HasWarnings);
Assert.Single(result.Warnings);

ProcessingWarning warning = result.Warnings[0];
Assert.Equal(ProcessingWarningType.MissingLoopCollection, warning.Type);
Assert.Equal("MissingItems", warning.VariableName);
Assert.Contains("loop: MissingItems", warning.Context);
}

[Fact]
public void ProcessTemplate_NullLoopCollection_CollectsWarning()
{
// Arrange
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("{{#foreach NullItems}}");
builder.AddParagraph("Item: {{.}}");
builder.AddParagraph("{{/foreach}}");

Dictionary<string, object?> data = new Dictionary<string, object?>
{
["NullItems"] = null
};

using MemoryStream templateStream = builder.ToStream();
using MemoryStream outputStream = new MemoryStream();

// Act
DocumentTemplateProcessor processor = new DocumentTemplateProcessor();
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data!);

// Assert
Assert.True(result.IsSuccess);
Assert.True(result.HasWarnings);
Assert.Single(result.Warnings);

ProcessingWarning warning = result.Warnings[0];
Assert.Equal(ProcessingWarningType.NullLoopCollection, warning.Type);
Assert.Equal("NullItems", warning.VariableName);
Assert.Contains("null", warning.Message.ToLowerInvariant());
}

[Fact]
public void ProcessTemplate_EmptyCollection_NoWarning()
{
// Arrange - empty collection should NOT produce a warning (by design)
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("{{#foreach EmptyItems}}");
builder.AddParagraph("Item: {{.}}");
builder.AddParagraph("{{/foreach}}");

Dictionary<string, object> data = new Dictionary<string, object>
{
["EmptyItems"] = new List<string>() // Empty, but valid
};

using MemoryStream templateStream = builder.ToStream();
using MemoryStream outputStream = new MemoryStream();

// Act
DocumentTemplateProcessor processor = new DocumentTemplateProcessor();
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Assert
Assert.True(result.IsSuccess);
Assert.False(result.HasWarnings);
Assert.Empty(result.Warnings);
}

[Fact]
public void ProcessTemplate_ValidData_NoWarnings()
{
// Arrange
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("Hello {{Name}}!");
builder.AddParagraph("{{#foreach Items}}");
builder.AddParagraph("Item: {{.}}");
builder.AddParagraph("{{/foreach}}");

Dictionary<string, object> data = new Dictionary<string, object>
{
["Name"] = "World",
["Items"] = new List<string> { "A", "B", "C" }
};

using MemoryStream templateStream = builder.ToStream();
using MemoryStream outputStream = new MemoryStream();

// Act
DocumentTemplateProcessor processor = new DocumentTemplateProcessor();
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Assert
Assert.True(result.IsSuccess);
Assert.False(result.HasWarnings);
Assert.Empty(result.Warnings);
}

[Fact]
public void ProcessTemplate_MixedWarnings_CollectsAll()
{
// Arrange - multiple warning types in one template
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("Hello {{MissingName}}!");
builder.AddParagraph("{{#foreach MissingItems}}");
builder.AddParagraph("Item: {{.}}");
builder.AddParagraph("{{/foreach}}");
builder.AddParagraph("{{#foreach NullItems}}");
builder.AddParagraph("Item: {{.}}");
builder.AddParagraph("{{/foreach}}");

Dictionary<string, object?> data = new Dictionary<string, object?>
{
["NullItems"] = null
};

using MemoryStream templateStream = builder.ToStream();
using MemoryStream outputStream = new MemoryStream();

// Act
DocumentTemplateProcessor processor = new DocumentTemplateProcessor();
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data!);

// Assert
Assert.True(result.IsSuccess);
Assert.True(result.HasWarnings);
Assert.Equal(3, result.Warnings.Count);

// Verify we have different warning types
Assert.Contains(result.Warnings, w => w.Type == ProcessingWarningType.MissingVariable);
Assert.Contains(result.Warnings, w => w.Type == ProcessingWarningType.MissingLoopCollection);
Assert.Contains(result.Warnings, w => w.Type == ProcessingWarningType.NullLoopCollection);
}

[Fact]
public void ProcessingWarning_ToString_ContainsUsefulInfo()
{
// Arrange
ProcessingWarning warning = ProcessingWarning.MissingVariable("TestVar");

// Act
string str = warning.ToString();

// Assert
Assert.Contains("MissingVariable", str);
Assert.Contains("TestVar", str);
}

[Fact]
public void ProcessTemplate_NestedMissingVariable_CollectsWarning()
{
// Arrange - missing nested path like Customer.Name
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("{{Customer.Name}}");

Dictionary<string, object> data = new Dictionary<string, object>();

using MemoryStream templateStream = builder.ToStream();
using MemoryStream outputStream = new MemoryStream();

// Act
DocumentTemplateProcessor processor = new DocumentTemplateProcessor();
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Assert
Assert.True(result.IsSuccess);
Assert.True(result.HasWarnings);
Assert.Single(result.Warnings);
Assert.Equal("Customer.Name", result.Warnings[0].VariableName);
}

#region Warning Report Tests

[Fact]
public void GetWarningReport_WithWarnings_ReturnsValidDocx()
{
// Arrange - create a result with warnings
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("Hello {{MissingName}}!");
builder.AddParagraph("{{#foreach MissingItems}}");
builder.AddParagraph("Item: {{.}}");
builder.AddParagraph("{{/foreach}}");

Dictionary<string, object> data = new Dictionary<string, object>();

using MemoryStream templateStream = builder.ToStream();
using MemoryStream outputStream = new MemoryStream();

DocumentTemplateProcessor processor = new DocumentTemplateProcessor();
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Act
using MemoryStream reportStream = result.GetWarningReport();

// Assert - verify it's a valid docx
Assert.True(reportStream.Length > 0);
reportStream.Position = 0;

using WordprocessingDocument doc = WordprocessingDocument.Open(reportStream, false);
Body? body = doc.MainDocumentPart?.Document.Body;
Assert.NotNull(body);

string text = body.InnerText;
Assert.Contains("Warning Report", text);
Assert.Contains("2", text); // Total 2 warnings
}

[Fact]
public void GetWarningReport_NoWarnings_ReturnsEmptyReport()
{
// Arrange - create a result with no warnings
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("Hello {{Name}}!");

Dictionary<string, object> data = new Dictionary<string, object>
{
["Name"] = "World"
};

using MemoryStream templateStream = builder.ToStream();
using MemoryStream outputStream = new MemoryStream();

DocumentTemplateProcessor processor = new DocumentTemplateProcessor();
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Act
using MemoryStream reportStream = result.GetWarningReport();

// Assert - verify it's a valid docx with 0 warnings
Assert.True(reportStream.Length > 0);
reportStream.Position = 0;

using WordprocessingDocument doc = WordprocessingDocument.Open(reportStream, false);
Body? body = doc.MainDocumentPart?.Document.Body;
Assert.NotNull(body);

string text = body.InnerText;
Assert.Contains("Warning Report", text);
Assert.Contains("Total Warnings: 0", text);
}

[Fact]
public void GetWarningReportBytes_WithWarnings_ReturnsByteArray()
{
// Arrange
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("Hello {{MissingVar}}!");

Dictionary<string, object> data = new Dictionary<string, object>();

using MemoryStream templateStream = builder.ToStream();
using MemoryStream outputStream = new MemoryStream();

DocumentTemplateProcessor processor = new DocumentTemplateProcessor();
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Act
byte[] reportBytes = result.GetWarningReportBytes();

// Assert
Assert.True(reportBytes.Length > 0);

// Verify it's a valid docx by loading it
using MemoryStream stream = new MemoryStream(reportBytes);
using WordprocessingDocument doc = WordprocessingDocument.Open(stream, false);
Assert.NotNull(doc.MainDocumentPart?.Document.Body);
}

[Fact]
public void GetWarningReport_MixedWarnings_ContainsAllSections()
{
// Arrange - create warnings of all types
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("{{MissingVar1}}");
builder.AddParagraph("{{MissingVar2}}");
builder.AddParagraph("{{#foreach MissingCollection}}{{.}}{{/foreach}}");
builder.AddParagraph("{{#foreach NullCollection}}{{.}}{{/foreach}}");

Dictionary<string, object?> data = new Dictionary<string, object?>
{
["NullCollection"] = null
};

using MemoryStream templateStream = builder.ToStream();
using MemoryStream outputStream = new MemoryStream();

DocumentTemplateProcessor processor = new DocumentTemplateProcessor();
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data!);

// Act
using MemoryStream reportStream = result.GetWarningReport();

// Assert
reportStream.Position = 0;
using WordprocessingDocument doc = WordprocessingDocument.Open(reportStream, false);
Body? body = doc.MainDocumentPart?.Document.Body;
Assert.NotNull(body);

string text = body.InnerText;

// Should contain all sections
Assert.Contains("Missing Variables", text);
Assert.Contains("Missing Loop Collections", text);
Assert.Contains("Null Loop Collections", text);

// Should contain the specific variable names
Assert.Contains("MissingVar1", text);
Assert.Contains("MissingVar2", text);
Assert.Contains("MissingCollection", text);
Assert.Contains("NullCollection", text);

// Should have correct counts
Assert.Contains("Total Warnings: 4", text);
}

#endregion
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no integration tests for ExpressionFailed warnings. The PR description claims "Failed expressions generate warnings" was tested, but the test file only includes tests for MissingVariable, MissingLoopCollection, and NullLoopCollection. Add tests to verify that ExpressionFailed warnings are properly collected when expression evaluation fails (e.g., when using an invalid expression in a placeholder).

Copilot uses AI. Check for mistakes.
Comment on lines 213 to 216
foreach (ProcessingWarning warning in result.Processing.Warnings.Take(5))
{
Results.Add($" - {missing}");
Results.Add($" - {warning.Type}: {warning.VariableName}");
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning summary display only shows the warning type and variable name, but not the full message. For ExpressionFailed warnings where the reason for failure is important, or for other warnings where additional context is valuable, users won't see the helpful error details. Consider including the warning message or at least a preview of it in the summary, especially since these are the first 5 warnings shown before suggesting users check the full report.

Copilot uses AI. Check for mistakes.
Address Copilot review comments by adding support for ExpressionFailed
warning type in the warning report:

- Add ExpressionFailed case to WarningReportGenerator switch statement
- Add "Failed Expressions" section to warning report template
- Add sample data for ExpressionFailed warnings in template generator
- Regenerate embedded WarningReportTemplate.docx resource
Address remaining Copilot review comments:

- Add integration tests for ExpressionFailed warnings (parse failures)
- Add test for valid expressions (no warning expected)
- Improve GUI warning summary to include truncated message text
  instead of just type and variable name
Add new documentation page covering:
- How to access warnings from ProcessingResult
- Warning types (MissingVariable, MissingLoopCollection,
  NullLoopCollection, ExpressionFailed)
- Warning properties
- Generating warning reports as Word documents
- Example code for logging warnings
- Behavior notes and edge cases
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 23 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@vaceslav vaceslav merged commit 859ec79 into main Jan 7, 2026
20 checks passed
@vaceslav vaceslav deleted the fix/normalize-loop-null-behavior branch January 7, 2026 20:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Normalize loop behavior for missing vs null collections

2 participants