diff --git a/javascript/packages/dev-tools/src/error-overlay.ts b/javascript/packages/dev-tools/src/error-overlay.ts index a14fdb345..1842c3b35 100644 --- a/javascript/packages/dev-tools/src/error-overlay.ts +++ b/javascript/packages/dev-tools/src/error-overlay.ts @@ -110,6 +110,20 @@ export class ErrorOverlay { const errorMap = new Map(); + // Extract LLM prompt and default view from separate template if present + const llmPromptTemplate = document.querySelector('template[data-herb-validation-llm-prompt]') as HTMLTemplateElement; + let llmPrompt: string | null = null; + let defaultView: 'human' | 'llm' = 'human'; + + if (llmPromptTemplate) { + llmPrompt = llmPromptTemplate.textContent?.trim() || llmPromptTemplate.innerHTML?.trim() || null; + const viewAttr = llmPromptTemplate.getAttribute('data-default-view'); + if (viewAttr === 'llm') { + defaultView = 'llm'; + } + templatesToRemove.push(llmPromptTemplate); + } + templates.forEach((template) => { try { const metadata = { @@ -147,7 +161,7 @@ export class ErrorOverlay { validationFragments.push(...errorMap.values()); if (validationFragments.length > 0) { - this.displayValidationOverlay(validationFragments); + this.displayValidationOverlay(validationFragments, llmPrompt, defaultView); } } @@ -427,11 +441,61 @@ export class ErrorOverlay { if (overlay) { document.body.appendChild(overlay); overlay.style.display = 'flex'; + this.setupParserErrorOverlayHandlers(overlay); } else { console.error('[ErrorOverlay] No parser error overlay found in HTML template'); } } + private setupParserErrorOverlayHandlers(overlay: HTMLElement) { + // Close overlay when clicking outside the container + overlay.addEventListener('click', (e) => { + if (e.target === overlay) { + overlay.style.display = 'none'; + document.body.style.overflow = ''; + } + }); + + // Close on Escape key + const escHandler = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + overlay.style.display = 'none'; + document.body.style.overflow = ''; + } + }; + document.addEventListener('keydown', escHandler); + + // Prevent body scroll when overlay is open + document.body.style.overflow = 'hidden'; + + // Section toggle functionality + const sectionHeaders = overlay.querySelectorAll('.herb-section-header'); + sectionHeaders.forEach(header => { + header.addEventListener('click', () => { + const sectionId = header.getAttribute('data-section-id'); + if (sectionId) { + const content = document.getElementById(sectionId + '-content'); + const toggle = document.getElementById(sectionId + '-toggle'); + + if (content && toggle) { + if (content.classList.contains('collapsed')) { + content.classList.remove('collapsed'); + toggle.classList.remove('collapsed'); + toggle.textContent = '▼'; + } else { + content.classList.add('collapsed'); + toggle.classList.add('collapsed'); + toggle.textContent = '▶'; + } + } + } + }); + }); + + this.setupViewToggle(overlay, '#herb-view-toggle', '#herb-human-view', '#herb-llm-view'); + this.setupCopyButton(overlay, '#herb-copy-button', '#herb-llm-prompt'); + } + private displayValidationOverlay(fragments: Array<{ metadata: { severity: string; @@ -446,7 +510,7 @@ export class ErrorOverlay { }; html: string; count: number; - }>) { + }>, llmPrompt: string | null, defaultView: 'human' | 'llm') { const existingOverlay = document.querySelector('.herb-validation-overlay'); if (existingOverlay) { existingOverlay.remove(); @@ -481,7 +545,9 @@ export class ErrorOverlay { fragments, errorsBySource, errorsByFile, - { errorCount, warningCount, totalCount, uniqueCount } + { errorCount, warningCount, totalCount, uniqueCount }, + llmPrompt, + defaultView ); const overlay = document.createElement('div'); @@ -497,7 +563,9 @@ export class ErrorOverlay { _fragments: Array, errorsBySource: Map, errorsByFile: Map, - counts: { errorCount: number; warningCount: number; totalCount: number; uniqueCount: number } + counts: { errorCount: number; warningCount: number; totalCount: number; uniqueCount: number }, + llmPrompt: string | null, + defaultView: 'human' | 'llm' ): string { let title = counts.uniqueCount === 1 ? 'Validation Issue' : `Validation Issues`; @@ -518,7 +586,7 @@ export class ErrorOverlay { const totalErrors = Array.from(errorsByFile.values()).reduce((sum, errors) => sum + errors.length, 0); fileTabs = ` -
+
@@ -538,10 +606,10 @@ export class ErrorOverlay {
${sourceFragments.map(f => { - const fileAttribute = `data-error-file="${this.escapeAttr(f.metadata.filename)}"`; + const fileAttribute = `data-error-file="${this.escapeAttr(f.metadata.filename)}"`; - if (f.count > 1) { - return ` + if (f.count > 1) { + return `
${f.html}
@@ -550,13 +618,24 @@ export class ErrorOverlay {
`; - } - return `
${f.html}
`; - }).join('')} + } + return `
${f.html}
`; + }).join('')}
`).join(''); + // Use server-provided LLM prompt (always present from Ruby side) + const finalLlmPrompt = llmPrompt || ''; + + // Determine visibility based on default view + const showLlm = defaultView === 'llm'; + const humanViewStyle = showLlm ? 'display: none;' : ''; + const llmViewStyle = showLlm ? '' : 'display: none;'; + const toggleButtonText = showLlm ? 'Human view' : 'LLM prompt'; + const fileTabsStyle = showLlm ? 'display: none;' : ''; + const dismissHintStyle = showLlm ? 'display: none;' : ''; + return `
@@ -568,13 +647,24 @@ export class ErrorOverlay {
${subtitle.join(', ')}
- +
+ + +
- ${fileTabs} -
+ ${fileTabs ? fileTabs.replace('
${contentSections}
-
+
+
+ + +
+
+
Click outside, press Esc key, or fix the code to dismiss.
You can also disable this overlay by passing validation_mode: :none to Herb::Engine. @@ -623,6 +713,19 @@ export class ErrorOverlay { display: flex; justify-content: space-between; align-items: flex-start; + gap: 16px; + } + + .herb-validation-header-content { + flex: 1; + min-width: 0; + } + + .herb-validation-header-actions { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; } .herb-validation-title { @@ -640,6 +743,24 @@ export class ErrorOverlay { margin-top: 4px; } + .herb-validation-overlay .herb-view-toggle { + color: rgba(255, 255, 255, 0.9); + font-family: inherit; + font-size: 13px; + font-weight: 500; + padding: 6px 12px; + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 6px; + transition: background-color 0.2s; + cursor: pointer; + white-space: nowrap; + } + + .herb-validation-overlay .herb-view-toggle:hover { + background: rgba(255, 255, 255, 0.2); + } + .herb-file-tabs { background: #1a1a1a; border-bottom: 1px solid #374151; @@ -872,6 +993,74 @@ export class ErrorOverlay { .herb-attr { color: #d19a66; } .herb-value { color: #98c379; } .herb-comment { color: #5c6370; font-style: italic; } + + /* LLM view styles */ + .herb-validation-llm-view { + display: flex; + flex-direction: column; + gap: 16px; + height: 100%; + } + + .herb-validation-overlay .herb-llm-textarea-container { + position: relative; + flex: 1; + display: flex; + flex-direction: column; + } + + .herb-validation-overlay .herb-copy-button { + position: absolute; + top: 8px; + right: 25px; + color: #9ca3af; + background: rgba(0, 0, 0, 0.5); + border: 1px solid #374151; + border-radius: 6px; + padding: 6px; + cursor: pointer; + transition: all 0.2s; + display: flex; + align-items: center; + justify-content: center; + z-index: 1; + } + + .herb-validation-overlay .herb-copy-button:hover { + color: #e5e5e5; + background: rgba(0, 0, 0, 0.7); + border-color: #6b7280; + } + + .herb-validation-overlay .herb-copy-button.copied { + color: #10b981; + border-color: #10b981; + } + + .herb-validation-overlay .herb-copy-button svg { + width: 18px; + height: 18px; + } + + .herb-validation-overlay .herb-llm-textarea { + flex: 1; + width: 100%; + min-height: 300px; + background: #111111; + border: 1px solid #374151; + border-radius: 8px; + color: #e5e5e5; + font-family: inherit; + font-size: 13px; + line-height: 1.6; + padding: 16px; + resize: none; + } + + .herb-validation-overlay .herb-llm-textarea:focus { + outline: none; + border-color: #6366f1; + } `; } @@ -937,6 +1126,90 @@ export class ErrorOverlay { }); }); }); + + // View toggle functionality (Human view <-> LLM view) + this.setupViewToggle( + overlay, + '.herb-validation-view-toggle', + '.herb-validation-human-view', + '.herb-validation-llm-view', + '.herb-validation-file-tabs', + '.herb-validation-dismiss-hint' + ); + + // Copy to clipboard functionality + this.setupCopyButton(overlay, '.herb-validation-copy-button', '.herb-validation-llm-prompt'); + } + + private setupViewToggle( + overlay: HTMLElement, + toggleSelector: string, + humanViewSelector: string, + llmViewSelector: string, + fileTabsSelector?: string, + dismissHintSelector?: string + ) { + const viewToggle = overlay.querySelector(toggleSelector) as HTMLButtonElement; + const humanView = overlay.querySelector(humanViewSelector) as HTMLElement; + const llmView = overlay.querySelector(llmViewSelector) as HTMLElement; + const fileTabs = fileTabsSelector ? overlay.querySelector(fileTabsSelector) as HTMLElement : null; + const dismissHint = dismissHintSelector ? overlay.querySelector(dismissHintSelector) as HTMLElement : null; + + if (viewToggle && humanView && llmView) { + viewToggle.addEventListener('click', () => { + const isShowingHuman = humanView.style.display !== 'none'; + + if (isShowingHuman) { + humanView.style.display = 'none'; + llmView.style.display = 'flex'; + viewToggle.textContent = 'Human view'; + if (fileTabs) fileTabs.style.display = 'none'; + if (dismissHint) dismissHint.style.display = 'none'; + } else { + humanView.style.display = 'flex'; + llmView.style.display = 'none'; + viewToggle.textContent = 'LLM prompt'; + if (fileTabs) fileTabs.style.display = 'flex'; + if (dismissHint) dismissHint.style.display = 'block'; + } + }); + } + } + + private setupCopyButton(overlay: HTMLElement, buttonSelector: string, textareaSelector: string) { + const copyButton = overlay.querySelector(buttonSelector) as HTMLButtonElement; + const textarea = overlay.querySelector(textareaSelector) as HTMLTextAreaElement; + + if (copyButton && textarea) { + const originalTitle = copyButton.getAttribute('title') || 'Copy to clipboard'; + const checkmarkSvg = ''; + const copySvg = copyButton.innerHTML; + + const showCopiedState = () => { + copyButton.innerHTML = checkmarkSvg; + copyButton.classList.add('copied'); + copyButton.setAttribute('title', 'Copied!'); + setTimeout(() => { + copyButton.innerHTML = copySvg; + copyButton.classList.remove('copied'); + copyButton.setAttribute('title', originalTitle); + }, 2000); + }; + + const fallbackCopy = () => { + textarea.select(); + document.execCommand('copy'); + showCopiedState(); + }; + + copyButton.addEventListener('click', () => { + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(textarea.value).then(showCopiedState).catch(fallbackCopy); + } else { + fallbackCopy(); + } + }); + } } private escapeAttr(text: string): string { diff --git a/lib/herb/engine.rb b/lib/herb/engine.rb index 967fd82f8..e83648198 100644 --- a/lib/herb/engine.rb +++ b/lib/herb/engine.rb @@ -51,6 +51,7 @@ def initialize(input, properties = {}) @content_for_head = properties[:content_for_head] @validation_error_template = nil @validation_mode = properties.fetch(:validation_mode, :raise) + @default_view = properties.fetch(:default_view, :human).to_sym @visitors = properties.fetch(:visitors, default_visitors) if @debug && @visitors.empty? @@ -67,6 +68,11 @@ def initialize(input, properties = {}) "validation_mode must be one of :raise, :overlay, or :none, got #{@validation_mode.inspect}" end + unless [:human, :llm].include?(@default_view) + raise ArgumentError, + "default_view must be one of :human or :llm, got #{@default_view.inspect}" + end + @freeze = properties[:freeze] @freeze_template_literals = properties.fetch(:freeze_template_literals, true) @text_end = @freeze_template_literals ? "'.freeze" : "'" @@ -321,6 +327,9 @@ def handle_validation_errors(errors, input) def add_validation_overlay(errors, input = nil) return unless errors.any? + # Group errors by file for LLM prompt generation + errors_by_file = {} #: Hash[String, Array[Hash[Symbol, untyped]]] + templates = errors.map { |error| location = error[:location] line = location&.start&.line || 0 @@ -333,6 +342,10 @@ def add_validation_overlay(errors, input = nil) escaped_message = escape_attr(error[:message]) escaped_suggestion = error[:suggestion] ? escape_attr(error[:suggestion]) : "" + # Track errors by file for LLM prompt + errors_by_file[@relative_file_path] ||= [] + errors_by_file[@relative_file_path] << error.merge(filename: @relative_file_path) + <<~TEMPLATE " + + @validation_error_template = templates + llm_template end def escape_attr(text) @@ -370,7 +394,8 @@ def add_parser_error_overlay(parser_errors, input) overlay_generator = ParserErrorOverlay.new( input, parser_errors, - filename: @relative_file_path + filename: @relative_file_path, + default_view: @default_view ) error_html = overlay_generator.generate_html diff --git a/lib/herb/engine/parser_error_overlay.rb b/lib/herb/engine/parser_error_overlay.rb index c42683bf0..b06d7782f 100644 --- a/lib/herb/engine/parser_error_overlay.rb +++ b/lib/herb/engine/parser_error_overlay.rb @@ -17,13 +17,14 @@ class ParserErrorOverlay Herb::Errors::MissingOpeningTagError ].freeze - def initialize(source, errors, filename: nil) + def initialize(source, errors, filename: nil, default_view: :human) @source = source @errors = errors.sort_by { |error| [ERROR_CLASS_PRIORITRY.index(error.class) || -1, error.location.start.line, error.location.start.column] } @filename = filename || "unknown" @lines = source.lines + @default_view = default_view.to_sym end def generate_html @@ -308,6 +309,95 @@ def generate_html border-radius: 3px; } + /* View toggle button */ + .herb-parser-error-overlay .herb-view-toggle { + color: rgba(255, 255, 255, 0.9); + font-family: inherit; + font-size: 13px; + font-weight: 500; + padding: 6px 12px; + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 6px; + transition: background-color 0.2s; + cursor: pointer; + white-space: nowrap; + flex-shrink: 0; + } + + .herb-parser-error-overlay .herb-view-toggle:hover { + background: rgba(255, 255, 255, 0.2); + } + + /* LLM view styles */ + .herb-parser-error-overlay .herb-llm-view { + display: flex; + flex-direction: column; + gap: 16px; + height: 100%; + } + + /* LLM textarea container */ + .herb-parser-error-overlay .herb-llm-textarea-container { + position: relative; + flex: 1; + display: flex; + flex-direction: column; + } + + .herb-parser-error-overlay .herb-copy-button { + position: absolute; + top: 8px; + right: 25px; + color: #9ca3af; + background: rgba(0, 0, 0, 0.5); + border: 1px solid #374151; + border-radius: 6px; + padding: 6px; + cursor: pointer; + transition: all 0.2s; + display: flex; + align-items: center; + justify-content: center; + z-index: 1; + } + + .herb-parser-error-overlay .herb-copy-button:hover { + color: #e5e5e5; + background: rgba(0, 0, 0, 0.7); + border-color: #6b7280; + } + + .herb-parser-error-overlay .herb-copy-button.copied { + color: #10b981; + border-color: #10b981; + } + + .herb-parser-error-overlay .herb-copy-button svg { + width: 18px; + height: 18px; + } + + .herb-parser-error-overlay .herb-llm-textarea { + flex: 1; + width: 100%; + min-height: 300px; + background: #111111; + border: 1px solid #374151; + border-radius: 8px; + color: #e5e5e5; + font-family: inherit; + font-size: 13px; + line-height: 1.6; + padding: 16px; + resize: none; + } + + .herb-parser-error-overlay .herb-llm-textarea:focus { + outline: none; + border-color: #6366f1; + } + @media (max-width: 768px) { .herb-parser-error-overlay { padding: 10px; @@ -342,11 +432,23 @@ def generate_html #{escape_html(error_message)}
+
-
+ + +
@@ -444,6 +591,75 @@ def generate_error_sections sections.uniq.join("\n") end + def generate_llm_prompt + error_count = @errors.length + error_word = error_count == 1 ? "error" : "errors" + + prompt = <<~MARKDOWN + # ERB Template Errors + + Herb, an HTML-aware ERB parsing tool, detected #{error_count} #{error_word} in an ERB template that need to be fixed. + + ## File + `#{@filename}` + + ## Errors + + MARKDOWN + + @errors.each_with_index do |error, index| + location = error.respond_to?(:location) && error.location ? error.location : nil + line_num = 1 + col_num = 1 + + if location.respond_to?(:start) && location.is_a?(Herb::Location) && location.start + line_num = location.start.line + col_num = location.start.column + end + + error_class = error.class.name.split("::").last.gsub(/Error$/, "") + error_message = error.respond_to?(:message) ? error.message : error.to_s + suggestion = get_error_suggestion(error) + + prompt += "### Error #{index + 1}: #{error_class}\n\n" + prompt += "**Location:** Line #{line_num}, Column #{col_num}\n\n" + prompt += "**Message:** #{error_message}\n\n" + prompt += "**Suggestion:** #{suggestion}\n\n" if suggestion + prompt += "**Code context:**\n\n```erb\n" + prompt += generate_code_snippet_for_llm(line_num) + prompt += "```\n\n" + end + + prompt += <<~MARKDOWN + ## Instructions + + Fix all the errors listed above. + + When fixing these errors, ensure that: + 1. All HTML tags are properly opened and closed + 2. Tags that span ERB control flow blocks are handled correctly + 3. The resulting HTML structure is valid + 4. Code duplication resulting from the fixes is kept to a minimum. Making use of methods (either View Component methods, helper methods, inline methods, etc.), HTML builder helper methods (ie: content_tag, etc.), procs, etc, if it means keeping the resulting code clean, readable and maintainable. + MARKDOWN + + prompt + end + + def generate_code_snippet_for_llm(error_line_num) + start_line = [error_line_num - CONTEXT_LINES, 1].max + end_line = [error_line_num + CONTEXT_LINES, @lines.length].min + + snippet = "" + (start_line..end_line).each do |i| + line = @lines[i - 1] || "" + line_str = line.chomp + marker = i == error_line_num ? " <-- ERROR" : "" + snippet += "#{i.to_s.rjust(4)}: #{line_str}#{marker}\n" + end + + snippet + end + def generate_code_section(error, index) location = error.respond_to?(:location) && error.location ? error.location : nil line_num = 1 diff --git a/lib/herb/engine/validation_error_overlay.rb b/lib/herb/engine/validation_error_overlay.rb index 40fbd7a15..f1cd5f3f2 100644 --- a/lib/herb/engine/validation_error_overlay.rb +++ b/lib/herb/engine/validation_error_overlay.rb @@ -53,6 +53,70 @@ def generate_fragment HTML end + #: (Array[Hash[Symbol, untyped]], Hash[String, Array[Hash[Symbol, untyped]]], Hash[Symbol, Integer]) -> String + def self.generate_llm_prompt(errors, errors_by_file, counts) + issue_word = counts[:total] == 1 ? "issue" : "issues" + files = errors_by_file.keys + + prompt = <<~MARKDOWN + # ERB Template Validation Issues + + Herb, an HTML-aware ERB validation tool, detected #{counts[:total]} #{issue_word} in #{files.length == 1 ? "an ERB template" : "#{files.length} ERB templates"} that should be addressed. + + MARKDOWN + + if counts[:errors].positive? && counts[:warnings].positive? + errors_word = counts[:errors] == 1 ? "error" : "errors" + warnings_word = counts[:warnings] == 1 ? "warning" : "warnings" + prompt += "**Summary:** #{counts[:errors]} #{errors_word}, #{counts[:warnings]} #{warnings_word}\n\n" + end + + prompt += "## Files\n" + files.each do |file| + prompt += "- `#{file}`\n" + end + prompt += "\n## Issues\n\n" + + errors.each_with_index do |error, index| + location = error[:location] + line_num = location&.start&.line || 1 + col_num = location&.start&.column || 1 + + severity_label = case error[:severity].to_s + when "error" then "Error" + when "warning" then "Warning" + else "Info" + end + source = error[:source].to_s.sub("Validator", "") + filename = error[:filename] || "unknown" + + prompt += "### Issue #{index + 1}: #{source} #{severity_label}\n\n" + prompt += "**File:** `#{filename}`\n\n" + prompt += "**Location:** Line #{line_num}, Column #{col_num}\n\n" + prompt += "**Message:** #{error[:message]}\n\n" + + prompt += "**Suggestion:** #{error[:suggestion]}\n\n" if error[:suggestion] + + count = error[:count] || 1 + prompt += "**Note:** This issue occurs #{count} times in the template.\n\n" if count > 1 + end + + prompt += <<~MARKDOWN + ## Instructions + + Please review and fix the validation issues listed above. + + When fixing these issues, ensure that: + 1. Security best practices are followed + 2. HTML structure follows proper nesting rules + 3. Code duplication resulting from the fixes is kept to a minimum. Making use of methods (either View Component methods, helper methods, inline methods, etc.), HTML builder helper methods (ie: content_tag, etc.), procs, etc, if it means keeping the resulting code clean, readable and maintainable. + + Provide correct the ERB template code for each affected file. + MARKDOWN + + prompt + end + private def generate_code_snippet(line_num, col_num) diff --git a/sig/herb/engine/parser_error_overlay.rbs b/sig/herb/engine/parser_error_overlay.rbs index 44c4ab225..f9a101fef 100644 --- a/sig/herb/engine/parser_error_overlay.rbs +++ b/sig/herb/engine/parser_error_overlay.rbs @@ -7,7 +7,7 @@ module Herb ERROR_CLASS_PRIORITRY: untyped - def initialize: (untyped source, untyped errors, ?filename: untyped) -> untyped + def initialize: (untyped source, untyped errors, ?filename: untyped, ?default_view: Symbol) -> untyped def generate_html: () -> untyped @@ -21,6 +21,10 @@ module Herb def generate_suggestions_section: (untyped suggestions) -> untyped + def generate_llm_prompt: () -> String + + def generate_code_snippet_for_llm: (untyped error_line_num) -> String + def syntax_highlight: (untyped code) -> untyped def highlight_with_tokens: (untyped tokens, untyped code) -> untyped diff --git a/sig/herb/engine/validation_error_overlay.rbs b/sig/herb/engine/validation_error_overlay.rbs index 32d41e3ec..e78a2fd0e 100644 --- a/sig/herb/engine/validation_error_overlay.rbs +++ b/sig/herb/engine/validation_error_overlay.rbs @@ -13,6 +13,8 @@ module Herb def generate_fragment: () -> untyped + def self.generate_llm_prompt: (Array[Hash[Symbol, untyped]], Hash[String, Array[Hash[Symbol, untyped]]], Hash[Symbol, Integer]) -> String + private def generate_code_snippet: (untyped line_num, untyped col_num) -> untyped diff --git a/test/snapshots/engine/validation_deduplication_test/test_0001_validation_errors_in_ERB_loops_generate_single_template_per_location_527dd13a0a4ec520816dc6bb5f4c7279.txt b/test/snapshots/engine/validation_deduplication_test/test_0001_validation_errors_in_ERB_loops_generate_single_template_per_location_527dd13a0a4ec520816dc6bb5f4c7279.txt index 98dc825a7..d109c1507 100644 --- a/test/snapshots/engine/validation_deduplication_test/test_0001_validation_errors_in_ERB_loops_generate_single_template_per_location_527dd13a0a4ec520816dc6bb5f4c7279.txt +++ b/test/snapshots/engine/validation_deduplication_test/test_0001_validation_errors_in_ERB_loops_generate_single_template_per_location_527dd13a0a4ec520816dc6bb5f4c7279.txt @@ -42,5 +42,5 @@ _buf = ::String.new; 10.times do |i| -'.html_safe).to_s; +'.html_safe).to_s; _buf.to_s diff --git a/test/snapshots/engine/validation_deduplication_test/test_0002_multiple_identical_errors_at_different_locations_generate_separate_templates_3412d8be45b5f990c1e1bff365ae57dc.txt b/test/snapshots/engine/validation_deduplication_test/test_0002_multiple_identical_errors_at_different_locations_generate_separate_templates_3412d8be45b5f990c1e1bff365ae57dc.txt index 96a6acc0a..9c989f870 100644 --- a/test/snapshots/engine/validation_deduplication_test/test_0002_multiple_identical_errors_at_different_locations_generate_separate_templates_3412d8be45b5f990c1e1bff365ae57dc.txt +++ b/test/snapshots/engine/validation_deduplication_test/test_0002_multiple_identical_errors_at_different_locations_generate_separate_templates_3412d8be45b5f990c1e1bff365ae57dc.txt @@ -146,5 +146,5 @@ _buf = ::String.new; _buf << '
-'.html_safe).to_s; +'.html_safe).to_s; _buf.to_s diff --git a/test/snapshots/engine/validation_deduplication_test/test_0003_validation_overlay_includes_deduplication_metadata_617be03c239297032907b0de8f4b03e9.txt b/test/snapshots/engine/validation_deduplication_test/test_0003_validation_overlay_includes_deduplication_metadata_617be03c239297032907b0de8f4b03e9.txt index 9a4b48c13..f31260734 100644 --- a/test/snapshots/engine/validation_deduplication_test/test_0003_validation_overlay_includes_deduplication_metadata_617be03c239297032907b0de8f4b03e9.txt +++ b/test/snapshots/engine/validation_deduplication_test/test_0003_validation_overlay_includes_deduplication_metadata_617be03c239297032907b0de8f4b03e9.txt @@ -41,5 +41,5 @@ _buf = ::String.new; _buf << '
-'.html_safe).to_s; +'.html_safe).to_s; _buf.to_s diff --git a/test/snapshots/engine/validation_modes_test/test_0005_overlay_mode_compiles_successfully_with_validation_errors_4546acae1ccf8092d2178e7ce803985e.txt b/test/snapshots/engine/validation_modes_test/test_0005_overlay_mode_compiles_successfully_with_validation_errors_4546acae1ccf8092d2178e7ce803985e.txt index cfbb6db7f..bcaa84ce3 100644 --- a/test/snapshots/engine/validation_modes_test/test_0005_overlay_mode_compiles_successfully_with_validation_errors_4546acae1ccf8092d2178e7ce803985e.txt +++ b/test/snapshots/engine/validation_modes_test/test_0005_overlay_mode_compiles_successfully_with_validation_errors_4546acae1ccf8092d2178e7ce803985e.txt @@ -41,5 +41,5 @@ _buf = ::String.new; _buf << '
-'.html_safe).to_s; +'.html_safe).to_s; _buf.to_s diff --git a/test/snapshots/engine/validation_modes_test/test_0008_overlay_mode_includes_filename_in_HTML_f0f5f57b1f0e9b7d0cd2bee98022629a.txt b/test/snapshots/engine/validation_modes_test/test_0008_overlay_mode_includes_filename_in_HTML_f0f5f57b1f0e9b7d0cd2bee98022629a.txt index e31f9bc90..372aff796 100644 --- a/test/snapshots/engine/validation_modes_test/test_0008_overlay_mode_includes_filename_in_HTML_f0f5f57b1f0e9b7d0cd2bee98022629a.txt +++ b/test/snapshots/engine/validation_modes_test/test_0008_overlay_mode_includes_filename_in_HTML_f0f5f57b1f0e9b7d0cd2bee98022629a.txt @@ -41,5 +41,5 @@ _buf = ::String.new; _buf << '
-'.html_safe).to_s; +'.html_safe).to_s; _buf.to_s diff --git a/test/snapshots/engine/validation_modes_test/test_0009_overlay_mode_with_multiple_validation_errors_e6a7c767c8906df797e04d39ba8f3806.txt b/test/snapshots/engine/validation_modes_test/test_0009_overlay_mode_with_multiple_validation_errors_e6a7c767c8906df797e04d39ba8f3806.txt index 68291fc9b..6e1c4b7a1 100644 --- a/test/snapshots/engine/validation_modes_test/test_0009_overlay_mode_with_multiple_validation_errors_e6a7c767c8906df797e04d39ba8f3806.txt +++ b/test/snapshots/engine/validation_modes_test/test_0009_overlay_mode_with_multiple_validation_errors_e6a7c767c8906df797e04d39ba8f3806.txt @@ -80,5 +80,5 @@ _buf = ::String.new; _buf << '
-'.html_safe).to_s; +'.html_safe).to_s; _buf.to_s